aboutsummaryrefslogtreecommitdiff
path: root/SrcShared/EmEventPlayback.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'SrcShared/EmEventPlayback.cpp')
-rw-r--r--SrcShared/EmEventPlayback.cpp1443
1 files changed, 1443 insertions, 0 deletions
diff --git a/SrcShared/EmEventPlayback.cpp b/SrcShared/EmEventPlayback.cpp
new file mode 100644
index 0000000..29c33cf
--- /dev/null
+++ b/SrcShared/EmEventPlayback.cpp
@@ -0,0 +1,1443 @@
+/* -*- mode: C++; tab-width: 4 -*- */
+/* ===================================================================== *\
+ Copyright (c) 1998-2001 Palm, Inc. or its subsidiaries.
+ All rights reserved.
+
+ This file is part of the Palm OS Emulator.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+\* ===================================================================== */
+
+#include "EmCommon.h"
+#include "EmEventPlayback.h"
+
+#include "CGremlinsStubs.h" // StubAppEnqueueKey, StubAppEnqueuePt
+#include "EmEventOutput.h" // GetEventInfo
+#include "EmMemory.h" // EmMem_strlen, EmMem_strcpy
+#include "EmMinimize.h" // EmMinimize::IsOn
+#include "EmPalmStructs.h" // EmAliasControlType
+#include "EmStreamFile.h" // EmStreamFile
+#include "Logging.h" // LogAppendMsg
+#include "ROMStubs.h" // EvtResetAutoOffTimer
+#include "SessionFile.h" // SessionFile
+
+#include <ctype.h> // isprint
+
+#if _DEBUG
+#define LOG_PLAYBACK 1
+#else
+#define LOG_PLAYBACK 0
+#endif
+
+#define PRINTF if (!LOG_PLAYBACK) ; else LogAppendMsg
+
+
+EmStream& operator << (EmStream&, const EmRecordedEvent&);
+EmStream& operator >> (EmStream&, EmRecordedEvent&);
+
+EmStream& operator << (EmStream&, const Chunk&);
+EmStream& operator >> (EmStream&, Chunk&);
+
+
+// List of events that we've recorded or re-read from a session file.
+
+Chunk EmEventPlayback::fgEvents;
+EmRecordedEventFilter EmEventPlayback::fgMask;
+Bool EmEventPlayback::fgRecording;
+Bool EmEventPlayback::fgReplaying;
+EmEventPlayback::EmIterationState EmEventPlayback::fgIterationState;
+EmEventPlayback::EmIterationState EmEventPlayback::fgPrevIterationState;
+
+enum EmStoredEventType
+{
+ // These values are written to external files, and so should not
+ // be changed. New values should be added to the end, and current
+ // values should not be deleted.
+
+ kStoredKeyEvent, // 7 bytes
+ kStoredKeyEventCompressed, // 2 byte
+
+ kStoredPenEvent, // 5 bytes
+ kStoredPenEventCompressedUp, // 1 bytes
+ kStoredPenEventCompressedX, // 4 bytes
+ kStoredPenEventCompressedY, // 4 bytes
+ kStoredPenEventCompressedXY, // 3 bytes
+
+ kStoredAppSwitchEvent, // 13 bytes
+ kStoredAppSwitchEventCompressed, // N/A
+
+ kStoredNullEvent, // 1 bytes
+ kStoredNullEventCompressed, // N/A
+
+ kStoredErrorEvent, // 5 bytes
+ kStoredErrorEventCompressed // N/A
+};
+
+
+static inline Bool PrvIsPenEvent (const EmRecordedEvent& event)
+{
+ return event.eType == kRecordedPenEvent;
+}
+
+static inline Bool PrvIsPenUp (const PointType& pt)
+{
+ return pt.x == -1 && pt.y == -1;
+}
+
+static inline Bool PrvIsPenDown (const PointType& pt)
+{
+ return !::PrvIsPenUp (pt);
+}
+
+static inline Bool PrvIsPenUp (const EmRecordedEvent& event)
+{
+ return ::PrvIsPenEvent (event) && ::PrvIsPenUp (event.penEvent.coords);
+}
+
+static inline Bool PrvIsPenDown (const EmRecordedEvent& event)
+{
+ return ::PrvIsPenEvent (event) && ::PrvIsPenDown (event.penEvent.coords);
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::Initialize
+// ---------------------------------------------------------------------------
+
+void EmEventPlayback::Initialize (void)
+{
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::Reset
+// ---------------------------------------------------------------------------
+
+void EmEventPlayback::Reset (void)
+{
+ fgRecording = false;
+ fgReplaying = false;
+
+ EmEventPlayback::ResetPlayback ();
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::Save
+// ---------------------------------------------------------------------------
+// Save our state to the given file. This method is called when a session
+// file is being saved to disk. A session file can be saved under the
+// following circumstances:
+//
+// * Save / Save As
+// * Minimization (after final minimizing)
+// * Gremlin switching
+//
+// For Save / Save As, we don't really care about saving events. In fact,
+// event recording shouldn't even be on. (However, this may change in the
+// future if we want to record human-generated events.)
+//
+// For Minimization, we want to save the final minimal set of events to a
+// .pev file so that it can later be replayed.
+//
+// For Gremlins, we want to save events to a holding .pev file, seperate
+// from the "resume" files Hordes creates.
+//
+// Based on these requirements, we don't really want to unconditionally save
+// events when saving the session. Instead, we'll save them as needed from
+// the various sub-systems that need the events.
+
+void EmEventPlayback::Save (SessionFile&)
+{
+// EmEventPlayback::SaveEvents (f, fgEvents);
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::Load
+// ---------------------------------------------------------------------------
+// Load our state from the given file. This method is called when a session
+// file is being reloaded from disk. A session file can be loaded under the
+// following circumstances:
+//
+// * Open
+// * Replay
+// * Minimize
+// * Gremlin switching
+//
+// For "Open", we don't really care about any events stored with the session.
+// In fact, since session files with saved events are usually of a type that
+// the Open menu item doesn't allow, we shouldn't get into this situation.
+//
+// When Replaying, we want to load the events so that we can replay them.
+//
+// When Minimizing, we want to load the events so that we can filter them --
+// possibly many times -- and then start replaying them. However, we want
+// to load the events only once, so as not to affect any modifications we
+// may have made to the event set as part of minimizing them.
+//
+// When Gremlin Switching, we want to load the events, but load them from
+// a file different from the one holding the state.
+//
+// Based on these requirements, we don't really want to unconditionally load
+// events when loading the session. Instead, we'll load them as needed from
+// the various sub-systems that need the events.
+
+void EmEventPlayback::Load (SessionFile&)
+{
+// EmEventPlayback::LoadEvents (f, fgEvents);
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::Dispose
+// ---------------------------------------------------------------------------
+
+void EmEventPlayback::Dispose (void)
+{
+ EmEventPlayback::Clear ();
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::SaveEvents
+// ---------------------------------------------------------------------------
+// Saves the given events to the given session file. Saves all of the events
+// in their own chunk.
+
+void EmEventPlayback::SaveEvents (SessionFile& f)
+{
+ const uint32 kCurrentVersion = 1;
+ Chunk chunk;
+ EmStreamChunk s (chunk);
+
+ s << kCurrentVersion;
+ s << fgEvents;
+
+ f.WriteGremlinHistory (chunk);
+
+#if 0
+ LogAppendMsg ("EmEventPlayback::SaveEvents: saved %d events", EmEventPlayback::GetNumEvents ());
+
+ EmRecordedEventList::size_type length = EmEventPlayback::GetNumEvents ();
+ EmRecordedEventList::size_type begin = length - 10;
+
+// if (begin < 0)
+ begin = 0;
+
+ for (EmRecordedEventList::size_type ii = begin; ii < length; ++ii)
+ {
+ LogAppendMsg ("%d:", ii);
+ EmEventPlayback::LogEvent (events[ii]);
+ }
+#endif
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::LoadEvents
+// ---------------------------------------------------------------------------
+// Loads all of the events from a session file and returns them in the given
+// collection.
+
+void EmEventPlayback::LoadEvents (const EmFileRef& ref)
+{
+ EmStreamFile stream (ref, kOpenExistingForRead);
+ ChunkFile chunkFile (stream);
+ SessionFile sessionFile (chunkFile);
+
+ EmEventPlayback::LoadEvents (sessionFile);
+}
+
+
+void EmEventPlayback::LoadEvents (SessionFile& f)
+{
+ Chunk chunk;
+
+ fgEvents.SetLength (0); // Clear the list in case of failure.
+
+ if (f.ReadGremlinHistory (chunk))
+ {
+ uint32 version;
+ EmStreamChunk s (chunk);
+
+ s >> version;
+ s >> fgEvents;
+
+ // Set the event mask to be the same size, with all events enabled.
+ // (I'd use assign() here, but it's not support on my Linux's version
+ // of STL.)
+// fgMask.assign (events.size (), true);
+ fgMask = EmRecordedEventFilter (EmEventPlayback::CountNumEvents (), true);
+ }
+
+#if 0
+ LogAppendMsg ("EmEventPlayback::LoadEvents: loaded %d events", EmEventPlayback::GetNumEvents ());
+
+ // Iterate over all the events, counting them up.
+
+ long ii = 0;
+ EmRecordedEvent event;
+ EmStreamChunk s (fgEvents);
+
+ while (s.GetMarker () < s.GetLength ())
+ {
+ s >> event;
+
+ LogAppendMsg ("%d:", ii);
+ EmEventPlayback::LogEvent (event);
+
+ ++ii;
+ }
+#endif
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::RecordEvents
+// ---------------------------------------------------------------------------
+
+void EmEventPlayback::RecordEvents (Bool record)
+{
+ fgRecording = record;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::RecordingEvents
+// ---------------------------------------------------------------------------
+// Return whether or not we're recording events.
+
+Bool EmEventPlayback::RecordingEvents (void)
+{
+ return fgRecording;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::RecordCharEvent
+// ---------------------------------------------------------------------------
+// Record a character event, as passed to EvtEnqueueKey.
+
+void EmEventPlayback::RecordKeyEvent (WChar ascii,
+ UInt16 keycode,
+ UInt16 modifiers)
+{
+ EmRecordedEvent event;
+ event.eType = kRecordedKeyEvent;
+
+ event.keyEvent.ascii = ascii;
+ event.keyEvent.keycode = keycode;
+ event.keyEvent.modifiers = modifiers;
+
+ EmEventPlayback::RecordEvent (event);
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::RecordPenEvent
+// ---------------------------------------------------------------------------
+// Record a pen event, as passed to EvtEnqueuePen.
+
+void EmEventPlayback::RecordPenEvent (const PointType& coords)
+{
+ EmAssert (::PrvIsPenUp (coords) || ((coords.x >= 0) && (coords.y >= 0)));
+
+ EmRecordedEvent event;
+ event.eType = kRecordedPenEvent;
+
+ event.penEvent.coords = coords;
+
+ EmEventPlayback::RecordEvent (event);
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::RecordSwitchEvent
+// ---------------------------------------------------------------------------
+// Record a psuedo-event indicating that Gremlins is trying to switch to
+// another application.
+
+void EmEventPlayback::RecordSwitchEvent (uint16 cardNo,
+ uint32 dbID,
+ uint16 oldCardNo,
+ uint32 oldDbID)
+{
+ EmRecordedEvent event;
+ event.eType = kRecordedAppSwitchEvent;
+
+ event.appSwitchEvent.cardNo = cardNo;
+ event.appSwitchEvent.dbID = dbID;
+ event.appSwitchEvent.oldCardNo = oldCardNo;
+ event.appSwitchEvent.oldDbID = oldDbID;
+
+ EmEventPlayback::RecordEvent (event);
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::RecordNullEvent
+// ---------------------------------------------------------------------------
+// Record a NULL event.
+
+void EmEventPlayback::RecordNullEvent (void)
+{
+ EmRecordedEvent event;
+ event.eType = kRecordedNullEvent;
+
+ EmEventPlayback::RecordEvent (event);
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::RecordErrorEvent
+// ---------------------------------------------------------------------------
+// Record a psuedo-event indicating that an error occurred.
+
+void EmEventPlayback::RecordErrorEvent (void)
+{
+ EmRecordedEvent event;
+ event.eType = kRecordedErrorEvent;
+
+ EmEventPlayback::RecordEvent (event);
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::Clear
+// ---------------------------------------------------------------------------
+// Clear all events that we've recorded so far.
+
+void EmEventPlayback::Clear (void)
+{
+ fgEvents.SetLength (0);
+ fgMask.clear ();
+ fgRecording = false;
+ fgReplaying = false;
+
+ EmEventPlayback::ResetPlayback ();
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::CullEvents
+// ---------------------------------------------------------------------------
+// Remove all filtered events.
+
+void EmEventPlayback::CullEvents (void)
+{
+ Chunk newEvents;
+ EmStreamChunk s (newEvents);
+
+ EmEventPlayback::ResetPlayback ();
+
+ EmRecordedEvent event;
+ while (EmEventPlayback::GetNextReplayEvent (event))
+ {
+ s << event;
+
+ // Keep track of pen up/down state so that GetNextReplayEvent
+ // will filter properly.
+
+ fgIterationState.fPenIsDown = ::PrvIsPenDown (event);
+ }
+
+ fgEvents = newEvents;
+
+ // Set the event mask to be the same size, with all events enabled.
+ // (I'd use assign() here, but it's not support on my Linux's version
+ // of STL.)
+// fgMask.assign (fgEvents.size (), true);
+ fgMask = EmRecordedEventFilter (EmEventPlayback::CountNumEvents (), true);
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::CountEnabledEvents
+// ---------------------------------------------------------------------------
+// If we were to play all events right now given the current enabled state,
+// return how many events that would be.
+
+long EmEventPlayback::CountEnabledEvents (void)
+{
+ long result = 0;
+
+ // We may be in the middle of a playback right now, so save the
+ // playback state we'll be changing.
+
+ EmValueChanger<EmIterationState> oldIterationState (fgIterationState, EmIterationState ());
+
+ // Iterate over all the events, counting them up.
+
+ EmRecordedEvent event;
+ while (EmEventPlayback::GetNextReplayEvent (event))
+ {
+ ++result;
+
+ // Keep track of pen up/down state so that GetNextReplayEvent
+ // will filter properly.
+
+ fgIterationState.fPenIsDown = ::PrvIsPenDown (event);
+ }
+
+ return result;
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::GetCurrentEvent
+// ---------------------------------------------------------------------------
+// Return the event number we've played back. This will be it's index plus
+// one. That is, it's the index of the event following the one we most
+// recently returned and presumably played.
+
+long EmEventPlayback::GetCurrentEvent (void)
+{
+ return fgIterationState.fIndex;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::GetNumEvents
+// ---------------------------------------------------------------------------
+// Return how many events we've recorded. If we're playing back, this count
+// may include events that have been masked out.
+
+long EmEventPlayback::GetNumEvents (void)
+{
+ return fgMask.size ();
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::CountNumEvents
+// ---------------------------------------------------------------------------
+// Return how many events we've recorded.
+
+long EmEventPlayback::CountNumEvents (void)
+{
+ long result = 0;
+
+ // Iterate over all the events, counting them up.
+
+ EmRecordedEvent event;
+ EmStreamChunk s (fgEvents);
+
+ while (s.GetMarker () < s.GetLength ())
+ {
+ s >> event;
+ ++result;
+ }
+
+ return result;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::GetEvent
+// ---------------------------------------------------------------------------
+// Return an event on our list of recorded events.
+
+void EmEventPlayback::GetEvent (long index, EmRecordedEvent& event)
+{
+ EmStreamChunk s (fgEvents);
+
+ while ((index-- >= 0) && (s.GetMarker () < s.GetLength ()))
+ {
+ s >> event;
+ }
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::EnableEvents
+// ---------------------------------------------------------------------------
+
+void EmEventPlayback::EnableEvents (long begin, long end)
+{
+ if (begin < 0)
+ begin = 0;
+
+ if (end > (long) fgMask.size ())
+ end = (long) fgMask.size ();
+
+ for (long ii = begin; ii < end; ++ii)
+ {
+ fgMask[ii] = true;
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::DisableEvents
+// ---------------------------------------------------------------------------
+
+void EmEventPlayback::DisableEvents (long begin, long end)
+{
+ if (begin < 0)
+ begin = 0;
+
+ if (end > (long) fgMask.size ())
+ end = (long) fgMask.size ();
+
+ for (long ii = begin; ii < end; ++ii)
+ {
+ fgMask[ii] = false;
+ }
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::ReplayEvents
+// ---------------------------------------------------------------------------
+
+void EmEventPlayback::ReplayEvents (Bool replay)
+{
+ fgReplaying = replay;
+
+ if (replay)
+ {
+ EmEventPlayback::ResetPlayback ();
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::ReplayingEvents
+// ---------------------------------------------------------------------------
+
+Bool EmEventPlayback::ReplayingEvents (void)
+{
+ return fgReplaying;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::ReplayGetEvent
+// ---------------------------------------------------------------------------
+
+Bool EmEventPlayback::ReplayGetEvent (void)
+{
+ EmAssert (EmEventPlayback::ReplayingEvents ());
+
+ Bool result = false;
+
+ EmRecordedEvent event;
+ if (EmEventPlayback::GetNextReplayEvent (event))
+ {
+ // Keep track of pen up/down state so that GetNextReplayEvent
+ // will filter properly.
+
+ fgIterationState.fPenIsDown = ::PrvIsPenDown (event);
+
+ switch (event.eType)
+ {
+ case kRecordedKeyEvent:
+
+ result = EmEventPlayback::ReplayKeyEvent (
+ event.keyEvent.ascii, event.keyEvent.keycode, event.keyEvent.modifiers);
+
+ break;
+
+ case kRecordedPenEvent:
+
+ result = EmEventPlayback::ReplayPenEvent (
+ event.penEvent.coords);
+
+ break;
+
+ case kRecordedAppSwitchEvent:
+
+ result = EmEventPlayback::ReplaySwitchEvent (
+ event.appSwitchEvent.cardNo, event.appSwitchEvent.dbID,
+ event.appSwitchEvent.oldCardNo, event.appSwitchEvent.oldDbID);
+
+ break;
+
+ case kRecordedNullEvent:
+
+ result = EmEventPlayback::ReplayNullEvent ();
+
+ break;
+
+ case kRecordedErrorEvent:
+
+ result = EmEventPlayback::ReplayErrorEvent ();
+
+ break;
+
+ default:
+ EmAssert (false);
+ break;
+ }
+
+ // Get information on this event.
+
+ EmEventOutput::GetEventInfo (event);
+ }
+ else
+ {
+ // No more events to return; turn ourself off.
+
+ EmEventPlayback::ReplayEvents (false);
+
+ // If minimization is running, tell it that we appear to have
+ // run the gamut.
+
+ if (EmMinimize::IsOn ())
+ {
+ EmMinimize::NoErrorOccurred ();
+ }
+ }
+
+ return result;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::ReplayGetPen
+// ---------------------------------------------------------------------------
+
+Bool EmEventPlayback::ReplayGetPen (void)
+{
+ EmAssert (EmEventPlayback::ReplayingEvents ());
+
+ EmRecordedEvent event;
+ Bool haveEvent = EmEventPlayback::GetNextReplayEvent (event);
+
+ // The Palm OS shouldn't be asking for the pen location if the
+ // pen is up. And if the pen is still down, there should be a
+ // pen up event in here somewhere. Therefore, GetNextReplayEvent
+ // should not be returning false or a non-pen event. But if it
+ // does (and it *does* happen), fabricate a pen up.
+
+ if (!haveEvent)
+ {
+ PRINTF ("EmEventPlayback::ReplayGetPen[%ld]: no more events; fabricating a pen up event",
+ fgIterationState.fIndex - 1);
+
+ haveEvent = true;
+ event.eType = kRecordedPenEvent;
+ event.penEvent.coords.x = -1;
+ event.penEvent.coords.y = -1;
+
+ fgIterationState.fPenIsDown = false;
+ }
+ else if (event.eType != kRecordedPenEvent)
+ {
+ PRINTF ("EmEventPlayback::ReplayGetPen[%ld]: next event wasn't a pen event; fabricating a pen up event",
+ fgIterationState.fIndex - 1);
+
+ EmAssert (fgPrevIterationState.fOffset != -1);
+
+ fgIterationState = fgPrevIterationState;
+
+ // Invalidate the previously saved iteration state so that
+ // we can make sure we don't use it again.
+
+ fgPrevIterationState.fOffset = -1;
+
+ event.eType = kRecordedPenEvent;
+ event.penEvent.coords.x = -1;
+ event.penEvent.coords.y = -1;
+
+ fgIterationState.fPenIsDown = false;
+ }
+
+ EmAssert (haveEvent);
+ EmAssert (event.eType == kRecordedPenEvent);
+
+ Bool result = EmEventPlayback::ReplayPenEvent (event.penEvent.coords);
+
+ // Get information on this event.
+
+ EmEventOutput::GetEventInfo (event);
+
+ return result;
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::RecordEvent
+// ---------------------------------------------------------------------------
+// Add the given event to our list of events, as long as recording is on.
+
+void EmEventPlayback::RecordEvent (const EmRecordedEvent& event)
+{
+ if (EmEventPlayback::RecordingEvents ())
+ {
+ EmStreamChunk s (fgEvents);
+ s.SetMarker (0, kStreamFromEnd);
+ s << event;
+
+ fgMask.push_back (true);
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::FindFirstError
+// ---------------------------------------------------------------------------
+// Return the index of the first error event record. Return -1 if one could
+// not be found.
+
+long EmEventPlayback::FindFirstError (void)
+{
+ long result = 0;
+
+ // Iterate over all the events, counting them up.
+
+ EmRecordedEvent event;
+ EmStreamChunk s (fgEvents);
+
+ while (s.GetMarker () < s.GetLength ())
+ {
+ s >> event;
+
+ if (event.eType == kRecordedErrorEvent)
+ {
+ return result;
+ }
+
+ ++result;
+ }
+
+ return -1;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::LogEvents
+// ---------------------------------------------------------------------------
+// Print debug information about the given event to the Log file.
+
+void EmEventPlayback::LogEvents (void)
+{
+ long counter = 0;
+ EmRecordedEvent event;
+ EmStreamChunk s (fgEvents);
+
+ while (s.GetMarker () < s.GetLength ())
+ {
+ s >> event;
+
+ LogAppendMsg ("%d:", counter);
+ EmEventPlayback::LogEvent (event);
+
+ ++counter;
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::LogEvent
+// ---------------------------------------------------------------------------
+// Print debug information about the given event to the Log file.
+
+void EmEventPlayback::LogEvent (const EmRecordedEvent& inEvent)
+{
+ static const char* kStrings[] =
+ {
+ "kGremlinKeyEvent",
+ "kGremlinPenEvent",
+ "kGremlinAppSwitchEvent",
+ "kGremlinNullEvent",
+ "kGremlinErrorEvent"
+ };
+
+ LogAppendMsg (" eType = %d (%s)", inEvent.eType, kStrings[inEvent.eType]);
+
+ switch (inEvent.eType)
+ {
+ case kRecordedKeyEvent:
+
+ // If it's a printable character, print it. Otherwise,
+ // show its numerical value.
+
+ if ((inEvent.keyEvent.ascii < 0x0100) && isprint (inEvent.keyEvent.ascii))
+ LogAppendMsg (" ascii = %d (%c)", inEvent.keyEvent.ascii, (char) inEvent.keyEvent.ascii);
+ else
+ LogAppendMsg (" ascii = %d", inEvent.keyEvent.ascii);
+
+ // If there's a keycode, print it.
+
+ if (inEvent.keyEvent.keycode)
+ LogAppendMsg (" keycode = %d", inEvent.keyEvent.keycode);
+
+ // If there's a modifier, print it.
+
+ if (inEvent.keyEvent.modifiers)
+ LogAppendMsg (" modifiers = %d", inEvent.keyEvent.modifiers);
+
+ break;
+
+ case kRecordedPenEvent:
+
+ // Print X, Y.
+
+ LogAppendMsg (" coords.x = %d", inEvent.penEvent.coords.x);
+ LogAppendMsg (" coords.y = %d", inEvent.penEvent.coords.y);
+
+ break;
+
+ case kRecordedAppSwitchEvent:
+
+ LogAppendMsg (" cardNo = %d", inEvent.appSwitchEvent.cardNo);
+ LogAppendMsg (" dbID = %d", inEvent.appSwitchEvent.dbID);
+ LogAppendMsg (" oldCardNo = %d", inEvent.appSwitchEvent.oldCardNo);
+ LogAppendMsg (" oldDbID = %d", inEvent.appSwitchEvent.oldDbID);
+
+ break;
+
+ case kRecordedNullEvent:
+
+ break;
+
+ case kRecordedErrorEvent:
+
+ break;
+
+ default:
+
+ EmAssert (false);
+ }
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::ResetPlayback
+// ---------------------------------------------------------------------------
+
+void EmEventPlayback::ResetPlayback (void)
+{
+ fgIterationState = EmIterationState ();
+
+ // Invalidate the previously saved iteration state so that
+ // we can make sure we don't use it.
+
+ fgPrevIterationState.fOffset = -1;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::GetNextReplayEvent
+// ---------------------------------------------------------------------------
+
+/*
+ The filtering of events is a little tricky. The user can *request*
+ that an event be filtered or not filtered during subsequent event
+ playback. However, this request does not need to be heeded by the
+ actual playback function. This disobedience is mostly noticable
+ when dealing with pen events. If the pen is down and the pen up
+ is supposed to be filtered out, the pen event will be replayed
+ anyway. Similarly, if a pen up event is supposed to be returned but
+ the pen is not down (because previous pen down events were filtered
+ out), then event is not replayed.
+*/
+
+Bool EmEventPlayback::GetNextReplayEvent (EmRecordedEvent& event)
+{
+ EmStreamChunk s (fgEvents);
+ s.SetMarker (fgIterationState.fOffset, kStreamFromStart);
+
+ // Save the current position so that we can push back to it
+ // later if we have to (see EmEventPlayback::ReplayGetPen).
+
+ fgPrevIterationState = fgIterationState;
+
+ while (s.GetMarker () < s.GetLength ())
+ {
+ s >> event;
+
+ Bool eventEnabled = fgMask[fgIterationState.fIndex];
+
+ fgIterationState.fIndex++;
+ fgIterationState.fOffset = s.GetMarker ();
+
+ // If this is a pen up event:
+ //
+ // * Discard it if the pen is already up.
+ //
+ // * Return it if the pen is down, even if this event is
+ // masked out.
+
+ if (::PrvIsPenUp (event))
+ {
+ if (!fgIterationState.fPenIsDown)
+ {
+ continue;
+ }
+
+ return true;
+ }
+
+ // If this is any event and it's not masked out, return it.
+
+ if (eventEnabled)
+ {
+ // Make sure that the pen is not down if this is not a pen event.
+ // The code above should make sure that we never get into this
+ // state.
+ //
+ // Actually, it seems that Gremlins can generate a sequence of
+ // pen down events followed by something other than a pen up
+ // event. So no longer make this assert. Elsewhere in this
+ // module, we'll have to make sure we set fPenIsDown to false
+ // on non-pen events.
+
+ /*
+ if (!::PrvIsPenEvent (event) && fgIterationState.fPenIsDown)
+ {
+ EmAssert (false);
+ }
+ */
+
+ return true;
+ }
+
+ // This event is masked out -- move on to the next event.
+ }
+
+ // No event to return.
+
+ return false;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::ReplayKeyEvent
+// ---------------------------------------------------------------------------
+
+Bool EmEventPlayback::ReplayKeyEvent (WChar ascii,
+ UInt16 keycode,
+ UInt16 modifiers)
+{
+ PRINTF ("EmEventPlayback::ReplayKeyEvent[%ld]: playing back key %d, %d, %d",
+ fgIterationState.fIndex - 1, ascii, keycode, modifiers);
+
+ // EvtEnqueueKey doesn't reset the event timer.
+
+ ::EvtResetAutoOffTimer ();
+
+ // Add the event to the event queue.
+
+ ::StubAppEnqueueKey (ascii, keycode, modifiers);
+
+ // Return that we posted an event.
+
+ return true;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::ReplayPenEvent
+// ---------------------------------------------------------------------------
+
+Bool EmEventPlayback::ReplayPenEvent (const PointType& pt)
+{
+ PRINTF ("EmEventPlayback::ReplayPenEvent[%ld]: playing back pen %d, %d",
+ fgIterationState.fIndex - 1, pt.x, pt.y);
+
+ // Add the event to the event queue.
+
+ ::StubAppEnqueuePt (&pt);
+
+ // Return that we posted an event.
+
+ return true;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::ReplaySwitchEvent
+// ---------------------------------------------------------------------------
+
+Bool EmEventPlayback::ReplaySwitchEvent (uint16 cardNo,
+ uint32 dbID,
+ uint16 /* oldCardNo */,
+ uint32 /* oldDbID */)
+{
+ PRINTF ("EmEventPlayback::ReplaySwitchEvent[%ld]: playing switch %d, %ld",
+ fgIterationState.fIndex - 1, cardNo, dbID);
+
+ // Switch to the indicated application.
+
+ ::SysUIAppSwitch (cardNo, dbID, sysAppLaunchCmdNormalLaunch, NULL);
+
+ // Return that we did NOT post an event, resulting in the event
+ // insertion mechanism posting a NULL event.
+
+ return false;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::ReplayNullEvent
+// ---------------------------------------------------------------------------
+
+Bool EmEventPlayback::ReplayNullEvent (void)
+{
+ PRINTF ("EmEventPlayback::ReplayNullEvent[%ld]: playing NULL",
+ fgIterationState.fIndex - 1);
+
+ // Return that we did NOT post an event, resulting in the event
+ // insertion mechanism posting a NULL event.
+
+ return false;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ EmEventPlayback::ReplayErrorEvent
+// ---------------------------------------------------------------------------
+
+Bool EmEventPlayback::ReplayErrorEvent (void)
+{
+ PRINTF ("EmEventPlayback::ReplayErrorEvent[%ld]: playing error",
+ fgIterationState.fIndex - 1);
+
+ // Return that we did NOT post an event, resulting in the event
+ // insertion mechanism posting a NULL event.
+
+ return false;
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ operator <<
+// ---------------------------------------------------------------------------
+// Flatten an EmRecordedEvent to the stream. Some events can be compressed by
+// not writing out fields that are commonly zero/NULL, or by writing out only
+// 8-bit values even though the field is a 16-bit type.
+
+EmStream& operator << (EmStream& inStream, const EmRecordedEvent& inEvent)
+{
+ switch (inEvent.eType)
+ {
+ case kRecordedKeyEvent:
+
+ if (inEvent.keyEvent.ascii < 256 &&
+ inEvent.keyEvent.keycode == 0 &&
+ inEvent.keyEvent.modifiers == 0)
+ {
+ inStream << (uint8) kStoredKeyEventCompressed;
+ inStream << (uint8) inEvent.keyEvent.ascii;
+ }
+ else
+ {
+ inStream << (uint8) kStoredKeyEvent;
+ inStream << inEvent.keyEvent.ascii;
+ inStream << inEvent.keyEvent.keycode;
+ inStream << inEvent.keyEvent.modifiers;
+ }
+
+ break;
+
+ case kRecordedPenEvent:
+
+ if (inEvent.penEvent.coords.x < 0 && inEvent.penEvent.coords.y < 0)
+ {
+ inStream << (uint8) kStoredPenEventCompressedUp;
+ }
+ else if (inEvent.penEvent.coords.x < 256 && inEvent.penEvent.coords.y < 256)
+ {
+ inStream << (uint8) kStoredPenEventCompressedXY;
+ inStream << (uint8) inEvent.penEvent.coords.x;
+ inStream << (uint8) inEvent.penEvent.coords.y;
+ }
+ else if (inEvent.penEvent.coords.x < 256)
+ {
+ inStream << (uint8) kStoredPenEventCompressedX;
+ inStream << (uint8) inEvent.penEvent.coords.x;
+ inStream << inEvent.penEvent.coords.y;
+ }
+ else if (inEvent.penEvent.coords.y < 256)
+ {
+ inStream << (uint8) kStoredPenEventCompressedX;
+ inStream << inEvent.penEvent.coords.x;
+ inStream << (uint8) inEvent.penEvent.coords.y;
+ }
+ else
+ {
+ inStream << (uint8) kStoredPenEvent;
+ inStream << inEvent.penEvent.coords.x;
+ inStream << inEvent.penEvent.coords.y;
+ }
+
+ break;
+
+ case kRecordedAppSwitchEvent:
+
+ inStream << (uint8) kStoredAppSwitchEvent;
+ inStream << inEvent.appSwitchEvent.cardNo;
+ inStream << inEvent.appSwitchEvent.dbID;
+ inStream << inEvent.appSwitchEvent.oldCardNo;
+ inStream << inEvent.appSwitchEvent.oldDbID;
+
+ break;
+
+ case kRecordedNullEvent:
+
+ inStream << (uint8) kStoredNullEvent;
+
+ break;
+
+ case kRecordedErrorEvent:
+
+ inStream << (uint8) kStoredErrorEvent;
+
+ break;
+
+ default:
+
+ EmAssert (false);
+ }
+
+ return inStream;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ operator >>
+// ---------------------------------------------------------------------------
+// Resurrect an EmRecordedEvent from the stream.
+
+EmStream& operator >> (EmStream& inStream, EmRecordedEvent& outEvent)
+{
+ uint8 temp;
+ uint8 storedType;
+ inStream >> storedType;
+
+ switch (storedType)
+ {
+ case kStoredKeyEvent:
+
+ outEvent.eType = kRecordedKeyEvent;
+
+ inStream >> outEvent.keyEvent.ascii;
+ inStream >> outEvent.keyEvent.keycode;
+ inStream >> outEvent.keyEvent.modifiers;
+
+ break;
+
+ case kStoredKeyEventCompressed:
+
+ outEvent.eType = kRecordedKeyEvent;
+
+ inStream >> temp;
+ outEvent.keyEvent.ascii = temp;
+ outEvent.keyEvent.keycode = 0;
+ outEvent.keyEvent.modifiers = 0;
+
+ break;
+
+ case kStoredPenEvent:
+
+ outEvent.eType = kRecordedPenEvent;
+
+ inStream >> outEvent.penEvent.coords.x;
+ inStream >> outEvent.penEvent.coords.y;
+
+ break;
+
+ case kStoredPenEventCompressedUp:
+
+ outEvent.eType = kRecordedPenEvent;
+ outEvent.penEvent.coords.x = -1;
+ outEvent.penEvent.coords.y = -1;
+
+ break;
+
+ case kStoredPenEventCompressedX:
+
+ outEvent.eType = kRecordedPenEvent;
+
+ inStream >> temp;
+ outEvent.penEvent.coords.x = temp;
+
+ inStream >> outEvent.penEvent.coords.y;
+
+ break;
+
+ case kStoredPenEventCompressedY:
+
+ outEvent.eType = kRecordedPenEvent;
+
+ inStream >> outEvent.penEvent.coords.x;
+
+ inStream >> temp;
+ outEvent.penEvent.coords.y = temp;
+
+ break;
+
+ case kStoredPenEventCompressedXY:
+
+ outEvent.eType = kRecordedPenEvent;
+
+ inStream >> temp;
+ outEvent.penEvent.coords.x = temp;
+
+ inStream >> temp;
+ outEvent.penEvent.coords.y = temp;
+
+ break;
+
+ case kStoredAppSwitchEvent:
+
+ outEvent.eType = kRecordedAppSwitchEvent;
+
+ inStream >> outEvent.appSwitchEvent.cardNo;
+ inStream >> outEvent.appSwitchEvent.dbID;
+ inStream >> outEvent.appSwitchEvent.oldCardNo;
+ inStream >> outEvent.appSwitchEvent.oldDbID;
+
+ break;
+
+ case kStoredAppSwitchEventCompressed:
+
+ EmAssert (false);
+
+ break;
+
+ case kStoredNullEvent:
+
+ outEvent.eType = kRecordedNullEvent;
+
+ break;
+
+ case kStoredNullEventCompressed:
+
+ EmAssert (false);
+
+ break;
+
+ case kStoredErrorEvent:
+
+ outEvent.eType = kRecordedErrorEvent;
+
+ break;
+
+ case kStoredErrorEventCompressed:
+
+ EmAssert (false);
+
+ break;
+
+ default:
+
+ EmAssert (false);
+ }
+
+ return inStream;
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ operator <<
+// ---------------------------------------------------------------------------
+// Flatten a Chunk to the stream.
+
+EmStream& operator << (EmStream& s, const Chunk& chunk)
+{
+ s << chunk.GetLength ();
+ s.PutBytes (chunk.GetPointer (), chunk.GetLength ());
+ return s;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ operator >>
+// ---------------------------------------------------------------------------
+// Resurrect a Chunk from the stream.
+
+EmStream& operator >> (EmStream& s, Chunk& chunk)
+{
+ long len;
+
+ s >> len;
+
+ chunk.SetLength (len);
+ s.GetBytes (chunk.GetPointer (), len);
+
+ return s;
+}
+
+
+EmRecordedEvent::EmRecordedEvent (void) :
+ eType (kRecordedUnknownEvent)
+{
+}
+
+EmRecordedEvent::EmRecordedEvent (const EmRecordedEvent& other)
+{
+ *this = other;
+}
+
+EmRecordedEvent::~EmRecordedEvent (void)
+{
+}
+
+EmRecordedEvent& EmRecordedEvent::operator= (const EmRecordedEvent& other)
+{
+ if (this != &other)
+ {
+ eType = other.eType;
+
+ switch (eType)
+ {
+ case kRecordedUnknownEvent:
+ EmAssert (false);
+ break;
+
+ case kRecordedKeyEvent:
+ keyEvent.ascii = other.keyEvent.ascii;
+ keyEvent.keycode = other.keyEvent.keycode;
+ keyEvent.modifiers = other.keyEvent.modifiers;
+ break;
+
+ case kRecordedPenEvent:
+ penEvent.coords = other.penEvent.coords;
+ break;
+
+ case kRecordedAppSwitchEvent:
+ appSwitchEvent.cardNo = other.appSwitchEvent.cardNo;
+ appSwitchEvent.dbID = other.appSwitchEvent.dbID;
+ appSwitchEvent.oldCardNo = other.appSwitchEvent.oldCardNo;
+ appSwitchEvent.oldDbID = other.appSwitchEvent.oldDbID;
+ break;
+
+ case kRecordedNullEvent:
+ break;
+
+ case kRecordedErrorEvent:
+ break;
+ }
+ }
+
+ return *this;
+}