aboutsummaryrefslogtreecommitdiff
path: root/SrcShared/Logging.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'SrcShared/Logging.cpp')
-rw-r--r--SrcShared/Logging.cpp1547
1 files changed, 1547 insertions, 0 deletions
diff --git a/SrcShared/Logging.cpp b/SrcShared/Logging.cpp
new file mode 100644
index 0000000..db84f3d
--- /dev/null
+++ b/SrcShared/Logging.cpp
@@ -0,0 +1,1547 @@
+/* -*- 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 "Logging.h"
+
+#include "EmApplication.h" // gApplication, IsBound
+#include "EmMemory.h" // EmMemGet32, EmMemGet16, EmMem_strcpy, EmMem_strncat
+#include "EmStreamFile.h" // EmStreamFile
+#include "Hordes.h" // Hordes::IsOn, Hordes::EventCounter
+#include "Platform.h" // GetMilliseconds
+#include "PreferenceMgr.h" // Preference<>
+#include "ROMStubs.h" // FrmGetTitle, WinGetFirstWindow
+#include "Strings.r.h" // kStr_LogFileSize
+#include "StringData.h" // virtual key descriptions
+
+#include <ctype.h> // isprint
+
+//#define LOG_TO_TRACE
+
+#ifdef LOG_TO_TRACE
+#include "TracerPlatform.h"
+#endif
+
+static LogStream* gStdLog;
+uint8 gLogCache[kCachedPrefKeyDummy];
+
+
+LogStream* LogGetStdLog (void)
+{
+ return gStdLog;
+}
+
+
+static void PrvPrefsChanged (PrefKeyType key, void*)
+{
+#define UPDATE_ONE_PREF(name) \
+ if (::PrefKeysEqual (key, kPrefKey##name)) \
+ { \
+ Preference<uint8> pref(kPrefKey##name, false); \
+ gLogCache[kCachedPrefKey##name] = *pref; \
+ }
+
+ FOR_EACH_SCALAR_PREF (UPDATE_ONE_PREF)
+}
+
+
+void LogStartup (void)
+{
+#define REGISTER_ONE_PREF(name) \
+ gPrefs->AddNotification (PrvPrefsChanged, kPrefKey##name); \
+ PrvPrefsChanged (kPrefKey##name, NULL);
+
+#define HARDCODE_ONE_PREF(name) \
+ gLogCache[kCachedPrefKey##name] = 0;
+
+ if (gApplication->IsBound ())
+ {
+ FOR_EACH_SCALAR_PREF (HARDCODE_ONE_PREF)
+ }
+ else
+ {
+ FOR_EACH_SCALAR_PREF (REGISTER_ONE_PREF)
+ }
+
+ EmAssert (gStdLog == NULL);
+ gStdLog = new LogStream ("Log");
+}
+
+
+void LogShutdown (void)
+{
+#define UNREGISTER_ONE_PREF(name) \
+ gPrefs->RemoveNotification (PrvPrefsChanged);
+
+ FOR_EACH_SCALAR_PREF (UNREGISTER_ONE_PREF)
+
+ EmAssert (gStdLog != NULL);
+ delete gStdLog; // Dumps it to a file, too.
+ gStdLog = NULL;
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ CLASS LogStream
+// ---------------------------------------------------------------------------
+
+const long kFindUniqueFile = -1;
+const uint32 kInvalidTimestamp = (uint32) -1;
+const int32 kInvalidGremlinCounter = -2;
+const long kEventTextMaxLen = 255;
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::LogStream
+ *
+ * DESCRIPTION: Constructor.
+ *
+ * PARAMETERS: baseName - base name to use for the file the data gets
+ * written to. This base name will be prepended with
+ * the path of the directory to write the file to, and
+ * appended with "%04d.txt", where %04d will be
+ * replaced with a number to make sure the file's
+ * name is unique.
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+LogStream::LogStream (const char* baseName) :
+ fMutex (),
+ fInner (baseName)
+{
+ gPrefs->AddNotification (PrefChanged, kPrefKeyLogFileSize, this);
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::~LogStream
+ *
+ * DESCRIPTION: Destructor. Writes any buffered text to the file and
+ * closes the file.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+LogStream::~LogStream (void)
+{
+ gPrefs->RemoveNotification (PrefChanged);
+
+ omni_mutex_lock lock (fMutex);
+ fInner.DumpToFile ();
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::Printf
+ *
+ * DESCRIPTION: A printf-like function for adding text to the log file.
+ * The text is preceded by a timestamp, and is suffixed
+ * with a newline.
+ *
+ * PARAMETERS: fmt - a printf-like string for formatting the output
+ * text.
+ *
+ * ... - additional printf-like parameters.
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+int LogStream::Printf (const char* fmt, ...)
+{
+ int n;
+ va_list arg;
+
+ omni_mutex_lock lock (fMutex);
+
+ va_start (arg, fmt);
+
+ n = fInner.VPrintf (fmt, arg);
+
+ va_end (arg);
+
+// fInner.DumpToFile ();
+ return n;
+}
+
+
+int LogStream::PrintfNoTime (const char* fmt, ...)
+{
+ int n;
+ va_list arg;
+
+ omni_mutex_lock lock (fMutex);
+
+ va_start (arg, fmt);
+
+ n = fInner.VPrintf (fmt, arg, false);
+
+ va_end (arg);
+
+// fInner.DumpToFile ();
+ return n;
+}
+
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::DataPrintf
+ *
+ * DESCRIPTION: A printf-like function for adding text to the log file.
+ * The text is preceded by a timestamp, and is suffixed
+ * with a newline.
+ *
+ * PARAMETERS: data - binary data to be included in the output
+ *
+ * dataLen - length of binary data
+ *
+ * fmt - a printf-like string for formatting the output
+ * text.
+ *
+ * ... - additional printf-like parameters.
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+int LogStream::DataPrintf (const void* data, long dataLen, const char* fmt, ...)
+{
+ omni_mutex_lock lock (fMutex);
+
+ int n;
+ va_list arg;
+
+ va_start (arg, fmt);
+
+ n = fInner.VPrintf (fmt, arg);
+
+ // Dump the data nicely formatted
+
+ n += fInner.DumpHex (data, dataLen);
+
+ return n;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::VPrintf
+ *
+ * DESCRIPTION: A vprintf-like function for adding text to the log file.
+ *
+ * PARAMETERS: fmt - a vprintf-like string for formatting the output
+ * text.
+ *
+ * args - additional vprintf-like parameters.
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+int LogStream::VPrintf (const char* fmt, va_list args)
+{
+ omni_mutex_lock lock (fMutex);
+
+ return fInner.VPrintf (fmt, args);
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::Write
+ *
+ * DESCRIPTION:
+ *
+ * PARAMETERS:
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+int LogStream::Write (const void* buffer, long size)
+{
+ omni_mutex_lock lock (fMutex);
+
+ return fInner.Write (buffer, size);
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::Clear
+ *
+ * DESCRIPTION: Clear any currently logged data
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStream::Clear (void)
+{
+ omni_mutex_lock lock (fMutex);
+
+ fInner.Clear ();
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::GetLogSize
+ *
+ * DESCRIPTION: Returns the maximum amount of text to be written to
+ * the log file.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: The maximum size.
+ *
+ ***********************************************************************/
+
+long LogStream::GetLogSize (void)
+{
+ omni_mutex_lock lock (fMutex);
+
+ return fInner.GetLogSize ();
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::SetLogSize
+ *
+ * DESCRIPTION: Sets the maximum amount of text to be written to the
+ * log file. Any currently logged data is lost.
+ *
+ * PARAMETERS: size - the new maximum value.
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStream::SetLogSize (long size)
+{
+ omni_mutex_lock lock (fMutex);
+
+ fInner.SetLogSize (size);
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::EnsureNewFile
+ *
+ * DESCRIPTION: Ensure that the logged data is written to a new file the
+ * next time DumpToFile is called. Otherwise, the data
+ * will be written to the same file it was written to the
+ * previous time DumpToFile was called.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStream::EnsureNewFile (void)
+{
+ omni_mutex_lock lock (fMutex);
+
+ fInner.EnsureNewFile ();
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::DumpToFile
+ *
+ * DESCRIPTION: Dumps any buffered text to the log file, prepending
+ * a message saying that only the last <mumble> bytes
+ * of text are buffered.
+ *
+ * If no data has been logged (or has been discarded with
+ * a call to Clear), nothing is written out and no file is
+ * created.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStream::DumpToFile (void)
+{
+ omni_mutex_lock lock (fMutex);
+
+ fInner.DumpToFile ();
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStream::PrefChanged
+ *
+ * DESCRIPTION: Outputs and EOL to the log stream.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStream::PrefChanged (PrefKeyType, PrefRefCon data)
+{
+ Preference<long> size (kPrefKeyLogFileSize);
+ ((LogStream*) data)->SetLogSize (*size);
+}
+
+
+#pragma mark -
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::LogStreamInner
+ *
+ * DESCRIPTION: Constructor.
+ *
+ * PARAMETERS: baseName - base name to use for the file the data gets
+ * written to. This base name will be prepended with
+ * the path of the directory to write the file to, and
+ * appended with "%04d.txt", where %04d will be
+ * replaced with a number to make sure the file's
+ * name is unique.
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+LogStreamInner::LogStreamInner (const char* baseName) :
+ fBaseName (baseName),
+ fFileIndex (kFindUniqueFile),
+ fBuffer (),
+ fBufferSize (0),
+ fDiscarded (false),
+ fLastGremlinEventCounter (kInvalidGremlinCounter),
+ fLastTimestampTime (kInvalidTimestamp),
+ fBaseTimestampTime (kInvalidTimestamp)
+{
+ Preference<long> size (kPrefKeyLogFileSize);
+ fBufferSize = *size;
+
+#ifdef LOG_TO_TRACE
+ gTracer.InitOutputPort ();
+#endif
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::~LogStreamInner
+ *
+ * DESCRIPTION: Destructor. Writes any buffered text to the file and
+ * closes the file.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+LogStreamInner::~LogStreamInner (void)
+{
+#ifdef LOG_TO_TRACE
+ gTracer.CloseOutputPort ();
+#else
+ this->DumpToFile ();
+#endif
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::DumpHex
+ *
+ * DESCRIPTION:
+ *
+ * PARAMETERS:
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+int LogStreamInner::DumpHex (const void* data, long dataLen)
+{
+ int n = 0;
+ const uint8* dataP = (const uint8*) data;
+
+ if (dataP && dataLen)
+ {
+ for (long ii = 0; ii < dataLen; ii += 16)
+ {
+ char text[16 * 4 + 4]; // 16 bytes * (3 for hex + 1 for ASCII) + 2 tabs + 1 space + 1 NULL
+ char* p = text;
+
+ *p++ = '\t';
+
+ // Print up to 16 bytes of hex on the left
+ long jj;
+ for (jj = ii; jj < ii + 16 ; ++jj)
+ {
+ if (jj < dataLen)
+ p += sprintf (p, "%02X ", dataP[jj]);
+ else
+ p += sprintf (p, " ");
+
+ if (jj == ii + 7)
+ p += sprintf (p, " ");
+
+ EmAssert (p - text < (ptrdiff_t) sizeof (text));
+ }
+
+ // Print the ascii on the right
+ *p++ = '\t';
+ for (jj = ii; jj < ii + 16 && jj < dataLen; ++jj)
+ {
+ char c = dataP[jj];
+ if (!isprint(c))
+ c = '.';
+ *p++ = c;
+
+ EmAssert (p - text < (ptrdiff_t) sizeof (text));
+ }
+
+ EmAssert (p - text <= (ptrdiff_t) sizeof (text));
+
+ this->Write (text, p - text);
+ }
+ }
+
+ return n;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::VPrintf
+ *
+ * DESCRIPTION: A vprintf-like function for adding text to the log file.
+ *
+ * PARAMETERS: fmt - a vprintf-like string for formatting the output
+ * text.
+ *
+ * args - additional vprintf-like parameters.
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+int LogStreamInner::VPrintf (const char* fmt, va_list args, Bool timestamp)
+{
+ char buffer[2000];
+
+ int n = vsprintf (buffer, fmt, args);
+
+ // debug check, watch for buffer overflows here
+ if (n < 0 || n >= (int) sizeof (buffer))
+ {
+ Platform::Debugger();
+ }
+
+ this->Write (buffer, n, timestamp);
+
+ return n;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::Write
+ *
+ * DESCRIPTION:
+ *
+ * PARAMETERS:
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+int LogStreamInner::Write (const void* buffer, long size, Bool timestamp)
+{
+ if (timestamp)
+ this->Timestamp ();
+
+ this->Append ((const char*) buffer, size);
+ this->NewLine ();
+
+ return size;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::Clear
+ *
+ * DESCRIPTION: Clear any currently logged data
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStreamInner::Clear (void)
+{
+ fBuffer.clear ();
+ fBaseTimestampTime = kInvalidTimestamp;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::GetLogSize
+ *
+ * DESCRIPTION: Returns the maximum amount of text to be written to
+ * the log file.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: The maximum size.
+ *
+ ***********************************************************************/
+
+long LogStreamInner::GetLogSize (void)
+{
+ return fBufferSize;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::SetLogSize
+ *
+ * DESCRIPTION: Sets the maximum amount of text to be written to the
+ * log file. Any currently logged data is lost.
+ *
+ * PARAMETERS: size - the new maximum value.
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStreamInner::SetLogSize (long size)
+{
+ fBufferSize = size;
+
+ this->TrimLeading ();
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::EnsureNewFile
+ *
+ * DESCRIPTION: Ensure that the logged data is written to a new file the
+ * next time DumpToFile is called. Otherwise, the data
+ * will be written to the same file it was written to the
+ * previous time DumpToFile was called.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStreamInner::EnsureNewFile (void)
+{
+ fFileIndex = kFindUniqueFile;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::DumpToFile
+ *
+ * DESCRIPTION: Dumps any buffered text to the log file, prepending
+ * a message saying that only the last <mumble> bytes
+ * of text are buffered.
+ *
+ * If no data has been logged (or has been discarded with
+ * a call to Clear), nothing is written out and no file is
+ * created.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStreamInner::DumpToFile (void)
+{
+ if (fBuffer.size () == 0)
+ return;
+
+ // Open the output stream. No need to open it as a "text" file;
+ // our Dump method does any host-specific conversion.
+
+ EmFileRef ref = this->CreateFileReference ();
+ EmStreamFile stream (ref, kCreateOrEraseForUpdate,
+ kFileCreatorCodeWarrior, kFileTypeText);
+
+ // If we lost data off the front print a message saying that only
+ // the last portion of the text is begin saved/dumped.
+
+ if (fDiscarded)
+ {
+ char buffer[200];
+ string templ = Platform::GetString (kStr_LogFileSize);
+ sprintf (buffer, templ.c_str (), fBufferSize / 1024L);
+ this->DumpToFile (stream, buffer, strlen (buffer));
+ }
+
+ // Dump the text.
+
+ const int kChunkSize = 1 * 1024L * 1024L;
+ StMemory chunk (kChunkSize);
+ ByteDeque::iterator iter = fBuffer.begin ();
+
+ while (iter != fBuffer.end ())
+ {
+ long amtToCopy = kChunkSize;
+ long amtLeft = fBuffer.end () - iter;
+
+ if (amtToCopy > amtLeft)
+ {
+ amtToCopy = amtLeft;
+ }
+
+ copy (iter, iter + amtToCopy, chunk.Get ());
+
+ this->DumpToFile (stream, chunk.Get (), amtToCopy);
+
+ iter += amtToCopy;
+ }
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::DumpToFile
+ *
+ * DESCRIPTION: Dumps the given text to the log file, converting any
+ * EOL characters along the way.
+ *
+ * PARAMETERS: f - open file to write the text to.
+ *
+ * s - text to write.
+ *
+ * size - number of characters to write (the input text
+ * is not necessarily NULL terminated).
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStreamInner::DumpToFile (EmStreamFile& f, const char* s, long size)
+{
+ StMemory converted;
+ long convertedLength;
+
+ Platform::ToHostEOL (converted, convertedLength, s, size);
+
+ f.PutBytes (converted.Get (), convertedLength);
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::CreateFileReference
+ *
+ * DESCRIPTION: Creates a file reference based on the base file name
+ * passed in to the constructor and the current fFileIndex.
+ * If fFileIndex is kFindUniqueFile, this routine attempts
+ * to find a file index that results in a new file being
+ * created. Otherwise, the current fFileIndex is used to
+ * either open an existing file or create a new one.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: The desired EmFileRef.
+ *
+ ***********************************************************************/
+
+EmFileRef LogStreamInner::CreateFileReference (void)
+{
+ EmFileRef result;
+ char buffer[32];
+
+ // Figure out where to put the log file. If a Gremlin Horde is
+ // running, then put the log file in the directory created to
+ // hold Gremlin output files. Otherwise, use the directory the
+ // use specified in the preferences. If no such directory was
+ // specified, use the Emulator's directory.
+
+ Preference<EmDirRef> logDirPref (kPrefKeyLogDefaultDir);
+
+ EmDirRef defaultDir = *logDirPref;
+ EmDirRef poserDir = EmDirRef::GetEmulatorDirectory ();
+ EmDirRef gremlinDir = Hordes::GetGremlinDirectory ();
+ EmDirRef logDir;
+
+ if (Hordes::IsOn ())
+ {
+ logDir = gremlinDir;
+ }
+ else if (defaultDir.Create (), defaultDir.Exists ())
+ {
+ logDir = defaultDir;
+ }
+ else
+ {
+ logDir = poserDir;
+ }
+
+ // If being forced to write to a new file, look for an unused
+ // file name.
+
+ if (fFileIndex == kFindUniqueFile)
+ {
+ fFileIndex = 0;
+
+ do
+ {
+ ++fFileIndex;
+
+ sprintf (buffer, "%s_%04ld.txt", fBaseName, fFileIndex);
+
+ result = EmFileRef (logDir, buffer);
+ }
+ while (result.IsSpecified () && result.Exists ());
+ }
+
+ // Otherwise, use the previously-used file name.
+
+ else
+ {
+ sprintf (buffer, "%s_%04ld.txt", fBaseName, fFileIndex);
+
+ result = EmFileRef (logDir, buffer);
+ }
+
+ return result;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::Timestamp
+ *
+ * DESCRIPTION: Outputs a timestamp to the log stream.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStreamInner::Timestamp (void)
+{
+ Bool reformat = false;
+ uint32 now = Platform::GetMilliseconds ();
+
+ // This may be a case of pre-optimization, but we try to keep around
+ // a formatted timestamp string for as long as possible. If either
+ // the time changes or the Gremlin event number changes, we force
+ // the regeneration of the cached timestamp string.
+
+ if (fLastTimestampTime != now)
+ reformat = true;
+
+ if (!reformat && Hordes::IsOn () && fLastGremlinEventCounter != Hordes::EventCounter ())
+ reformat = true;
+
+ if (reformat)
+ {
+ fLastTimestampTime = now;
+
+ // We try to print out logged data with timestamps that are
+ // relative to the first event recorded.
+
+ if (fBaseTimestampTime == kInvalidTimestamp)
+ fBaseTimestampTime = now;
+
+ now -= fBaseTimestampTime;
+
+ // If a Gremlin is running, use a formatting string that includes
+ // the event number. Otherwise, use a format string that omits it.
+
+ if (Hordes::IsOn ())
+ {
+ fLastGremlinEventCounter = Hordes::EventCounter ();
+ sprintf (fLastTimestampString, "%ld.%03ld (%ld):\t", now / 1000, now % 1000, fLastGremlinEventCounter);
+ }
+ else
+ {
+ sprintf (fLastTimestampString, "%ld.%03ld:\t", now / 1000, now % 1000);
+ }
+ }
+
+ this->Append (fLastTimestampString, strlen (fLastTimestampString));
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::NewLine
+ *
+ * DESCRIPTION: Outputs and EOL to the log stream.
+ *
+ * PARAMETERS: none
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStreamInner::NewLine (void)
+{
+ this->Append ("\n", 1);
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::Append
+ *
+ * DESCRIPTION: Generic function for adding text (actually, any kind of
+ * unformatted data) to the output stream. If the amount
+ * of text in the buffer exceeds the maximum specified
+ * amount, any old text is deleted. This function is
+ * the bottleneck for all such functions in this class.
+ *
+ * PARAMETERS: buffer - pointer to the text to be added.
+ *
+ * size - length of the text (in bytes) to be added.
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStreamInner::Append (const char* buffer, long size)
+{
+ if (size != 0)
+ {
+#ifdef LOG_TO_TRACE
+ // Convert the text to a string
+
+ string s (buffer, size);
+
+ // Write out the string, breaking it up manually at
+ // '\n's (OutputVT doesn't know that's what we're using
+ // for line endings).
+
+ string::size_type pos = s.find ('\n');
+
+ while (pos != string::npos)
+ {
+ string substr = s.substr (0, pos) + " ";
+ gTracer.OutputVTL (0, substr.c_str (), (va_list) NULL);
+
+ s = s.substr (pos + 1);
+ pos = s.find ('\n');
+ }
+
+ // If there's anything left, write it out without the CR.
+
+ if (s.size () > 0)
+ {
+ gTracer.OutputVT (0, s.c_str (), (va_list) NULL);
+ }
+#else
+ copy (buffer, buffer + size, back_inserter (fBuffer));
+
+ this->TrimLeading ();
+#endif
+ }
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: LogStreamInner::TrimLeading
+ *
+ * DESCRIPTION: If the buffer has exceeded the maximum size we've
+ * established for it, drop any leading characters.
+ *
+ * PARAMETERS: none.
+ *
+ * RETURNED: nothing
+ *
+ ***********************************************************************/
+
+void LogStreamInner::TrimLeading (void)
+{
+ long amtToDiscard = fBuffer.size () - fBufferSize;
+
+ if (amtToDiscard > 0)
+ {
+ fDiscarded = true;
+ fBuffer.erase (fBuffer.begin (), fBuffer.begin () + amtToDiscard);
+
+ // Keep chopping up to the next '\n' so that we don't leave
+ // any partial lines.
+
+ while (fBuffer.front () != '\n')
+ {
+ fBuffer.pop_front ();
+ }
+
+ fBuffer.pop_front ();
+ }
+}
+
+
+#pragma mark -
+
+// ---------------------------------------------------------------------------
+// ¥ StubEmFrmGetTitle
+// ---------------------------------------------------------------------------
+// Returns a pointer to the title string of a form. Copied from Form.c.
+
+static string StubEmFrmGetTitle (const FormPtr frm)
+{
+ const Char* title = FrmGetTitle (frm);
+
+ if (title)
+ {
+ char buffer[256];
+ EmMem_strcpy (buffer, (emuptr) title);
+ return string (buffer);
+ }
+
+ return string ("Untitled");
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ StubEmPrintFormID
+// ---------------------------------------------------------------------------
+// Displays the form resource id associated with the window passed.
+
+static void StubEmPrintFormID (WinHandle winHandle, char* desc, char* eventText)
+{
+ emuptr winPtr;
+ emuptr exitWinPtr;
+
+ // Allow access to WindowType fields windowFlags and nextWindow.
+
+ CEnableFullAccess munge;
+
+ if (winHandle)
+ {
+ exitWinPtr = (emuptr) WinGetWindowPointer (winHandle);
+
+ // Check if the handle is still valid. If the form has been deleted
+ // then we can't dereference the window pointer.
+
+ // Search the window list for the pointer.
+ winHandle = WinGetFirstWindow ();
+ while (winHandle)
+ {
+ winPtr = (emuptr) WinGetWindowPointer (winHandle);
+ if (winPtr == exitWinPtr)
+ break;
+
+ winHandle = (WinHandle) EmMemGet32 (winPtr + offsetof (WindowType, nextWindow));
+ }
+
+
+ if (winHandle && /*winPtr->windowFlags.dialog*/
+ ((EmMemGet16 (winPtr + offsetof (WindowType, windowFlags)) & 0x0200) != 0))
+ {
+ string title = StubEmFrmGetTitle((FormPtr) winPtr);
+ if (!title.empty())
+ sprintf (&eventText[strlen(eventText)],"%s: \"%s\"", desc, title.c_str());
+ else
+ sprintf (&eventText[strlen(eventText)],"%s ID: %ld", desc, /*frm->formId*/
+ EmMemGet16 (winPtr + offsetof (FormType, formId)));
+ }
+ }
+}
+
+
+
+#define irObCloseChr 0x01FB // to shut down Obex from background thread
+
+
+
+
+static const char* StubEmKeyDescription (Int16 key)
+{
+ unsigned int index;
+
+ index = key - vchrLowBattery;
+
+ if (index < gVirtualKeyDescriptionsCount)
+ return kVirtualKeyDescriptions [index];
+
+ index = key - irObCloseChr;
+
+ if (index < gHardKeyDescriptionsCount)
+ return kHardKeyDescriptions [index];
+
+ return "";
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ PrvGetEventText
+// ---------------------------------------------------------------------------
+// Displays the passed event in the emulator's event tracewindow if it is
+// active.
+
+static Bool PrvGetEventText(const EventType* eventP, char* eventText)
+{
+ long curLen = strlen (eventText);
+ eventText += curLen;
+
+ switch (eventP->eType)
+ {
+ case nilEvent:
+ return false;
+ //sprintf(eventText,"nilEvent");
+ //break;
+
+ case penDownEvent:
+ sprintf(eventText,"penDownEvent X:%d Y:%d",
+ eventP->screenX, eventP->screenY);
+ break;
+
+ case penUpEvent:
+ strcpy(eventText,"penUpEvent");
+ sprintf(eventText,"penUpEvent X:%d Y:%d",
+ eventP->screenX, eventP->screenY);
+ break;
+
+ case penMoveEvent:
+ strcpy(eventText,"penMoveEvent");
+ sprintf(eventText,"penMoveEvent X:%d Y:%d",
+ eventP->screenX, eventP->screenY);
+ break;
+
+ case keyDownEvent:
+ if ((eventP->data.keyDown.chr < 0x0100) && isprint (eventP->data.keyDown.chr))
+ {
+ sprintf(eventText,"keyDownEvent Key:'%c' 0x%02X%s, Modifiers: 0x%04X",
+ (char) eventP->data.keyDown.chr, eventP->data.keyDown.chr,
+ StubEmKeyDescription(eventP->data.keyDown.chr),
+ eventP->data.keyDown.modifiers);
+ }
+ else
+ {
+ sprintf(eventText,"keyDownEvent Key:0x%02X%s, Modifiers: 0x%04X",
+ eventP->data.keyDown.chr,
+ StubEmKeyDescription(eventP->data.keyDown.chr),
+ eventP->data.keyDown.modifiers);
+ }
+ break;
+
+ case winEnterEvent:
+ sprintf(eventText,"winEnterEvent Enter: %p Exit: %p",
+ eventP->data.winEnter.enterWindow, eventP->data.winEnter.exitWindow);
+ StubEmPrintFormID (eventP->data.winEnter.enterWindow, " Enter Form", eventText);
+ StubEmPrintFormID (eventP->data.winEnter.exitWindow, " Exit Form", eventText);
+ break;
+
+ case winExitEvent:
+ sprintf(eventText,"winExitEvent Enter: %p Exit: %p",
+ eventP->data.winExit.enterWindow, eventP->data.winExit.exitWindow);
+ StubEmPrintFormID (eventP->data.winExit.enterWindow, " Enter Form", eventText);
+ StubEmPrintFormID (eventP->data.winExit.exitWindow, " Exit Form", eventText);
+ break;
+
+ case ctlEnterEvent:
+ sprintf(eventText,"ctlEnterEvent ID: %u",
+ eventP->data.ctlEnter.controlID);
+ break;
+
+ case ctlSelectEvent:
+ sprintf(eventText,"ctlSelectEvent ID: %u On: %u",
+ eventP->data.ctlSelect.controlID, eventP->data.ctlSelect.on);
+ break;
+
+ case ctlRepeatEvent:
+ sprintf(eventText,"ctlRepeatEvent ID: %u Time: %lu",
+ eventP->data.ctlRepeat.controlID, eventP->data.ctlRepeat.time);
+ break;
+
+ case ctlExitEvent:
+ sprintf(eventText,"ctlExitEvent");
+ break;
+
+ case lstEnterEvent:
+ sprintf(eventText,"lstEnterEvent ID: %u Item: %u",
+ eventP->data.lstEnter.listID, eventP->data.lstEnter.selection);
+ break;
+
+ case lstSelectEvent:
+ sprintf(eventText,"lstSelectEvent ID: %u Item: %u",
+ eventP->data.lstSelect.listID, eventP->data.lstSelect.selection);
+ break;
+
+ case lstExitEvent:
+ sprintf(eventText,"lstExitEvent ID: %u",
+ eventP->data.lstExit.listID);
+ break;
+
+ case popSelectEvent:
+ sprintf(eventText,"popSelectEvent CtlID: %u ListID: %u Item: %u",
+ eventP->data.popSelect.controlID, eventP->data.popSelect.listID,
+ eventP->data.popSelect.selection);
+ break;
+
+ case fldEnterEvent:
+ sprintf(eventText,"fldEnterEvent ID: %u",
+ eventP->data.fldEnter.fieldID);
+ break;
+
+ case fldHeightChangedEvent:
+ sprintf(eventText,"fldHeightChangedEvent ID: %u Height: %u Pos: %u",
+ eventP->data.fldHeightChanged.fieldID,
+ eventP->data.fldHeightChanged.newHeight,
+ eventP->data.fldHeightChanged.currentPos);
+ break;
+
+ case fldChangedEvent:
+ sprintf(eventText,"fldChangedEvent ID: %u",
+ eventP->data.fldChanged.fieldID);
+ break;
+
+ case tblEnterEvent:
+ sprintf(eventText,"tblEnterEvent ID: %u Row: %u Col: %u",
+ eventP->data.tblEnter.tableID,
+ eventP->data.tblEnter.row,
+ eventP->data.tblEnter.column);
+ break;
+
+ case tblSelectEvent:
+ sprintf(eventText,"tblSelectEvent ID: %u Row: %u Col: %u",
+ eventP->data.tblSelect.tableID,
+ eventP->data.tblSelect.row,
+ eventP->data.tblSelect.column);
+ break;
+
+ case tblExitEvent:
+ sprintf(eventText,"tblExitEvent ID: %u Row: %u Col: %u",
+ eventP->data.tblExit.tableID,
+ eventP->data.tblExit.row,
+ eventP->data.tblExit.column);
+ break;
+
+ case daySelectEvent:
+ strcpy(eventText,"daySelectEvent");
+ break;
+
+ case menuEvent:
+ sprintf(eventText,"menuEvent ItemID: %u",
+ eventP->data.menu.itemID);
+ break;
+
+ case appStopEvent:
+ strcpy(eventText,"appStopEvent");
+ break;
+
+ case frmLoadEvent:
+ sprintf(eventText,"frmLoadEvent ID: %u",
+ eventP->data.frmOpen.formID);
+ break;
+
+ case frmOpenEvent:
+ sprintf(eventText,"frmOpenEvent ID: %u",
+ eventP->data.frmOpen.formID);
+ break;
+
+ case frmGotoEvent:
+ sprintf(eventText,"frmGotoEvent ID: %u Record: %u Field: %u",
+ eventP->data.frmGoto.formID,
+ eventP->data.frmGoto.recordNum,
+ eventP->data.frmGoto.matchFieldNum);
+ break;
+
+ case frmUpdateEvent:
+ sprintf(eventText,"frmUpdateEvent ID: %u",
+ eventP->data.frmUpdate.formID);
+ break;
+
+ case frmSaveEvent:
+ sprintf(eventText,"frmSaveEvent");
+ break;
+
+ case frmCloseEvent:
+ sprintf(eventText,"frmCloseEvent ID: %u",
+ eventP->data.frmClose.formID);
+ break;
+
+ case frmTitleEnterEvent:
+ sprintf(eventText,"frmTitleEnterEvent ID: %u",
+ eventP->data.frmTitleEnter.formID);
+ break;
+
+ case frmTitleSelectEvent:
+ sprintf(eventText,"frmTitleSelectEvent ID: %u",
+ eventP->data.frmTitleSelect.formID);
+ break;
+
+ case sclEnterEvent:
+ sprintf(eventText,"sclEnterEvent ID: %u",
+ eventP->data.sclEnter.scrollBarID);
+ break;
+
+ case sclRepeatEvent:
+ sprintf(eventText,"sclRepeatEvent ID: %u Value: %u, New value: %u",
+ eventP->data.sclRepeat.scrollBarID,
+ eventP->data.sclRepeat.value,
+ eventP->data.sclRepeat.newValue);
+ break;
+
+ case sclExitEvent:
+ sprintf(eventText,"sclExitEvent ID: %u",
+ eventP->data.sclExit.scrollBarID);
+ break;
+
+ case tsmConfirmEvent:
+ curLen += sprintf(eventText,"tsmConfirmEvent ID: %u Text: ",
+ eventP->data.tsmConfirm.formID);
+ EmMem_strncat(eventText, (emuptr)eventP->data.tsmConfirm.yomiText, kEventTextMaxLen - curLen);
+ eventText[kEventTextMaxLen] = 0; // Make sure we're terminated
+ break;
+
+ case tsmFepButtonEvent:
+ sprintf(eventText,"tsmFepButtonEvent ID: %u",
+ eventP->data.tsmFepButton.buttonID);
+ break;
+
+ case tsmFepModeEvent:
+ sprintf(eventText,"tsmFepModeEvent ID: %u",
+ eventP->data.tsmFepMode.mode);
+ break;
+
+ case menuCmdBarOpenEvent:
+ sprintf(eventText,"menuCmdBarOpenEvent preventFieldButtons: %u",
+ eventP->data.menuCmdBarOpen.preventFieldButtons);
+ break;
+
+ case menuOpenEvent:
+ sprintf(eventText,"menuOpenEvent RscID:%u, cause:%u",
+ eventP->data.menuOpen.menuRscID,
+ eventP->data.menuOpen.cause);
+ break;
+
+ case menuCloseEvent:
+ sprintf(eventText,"menuCloseEvent");
+ break;
+
+ case frmGadgetEnterEvent:
+ sprintf(eventText,"frmGadgetEnterEvent RscID:%u, gadget:0x%08lX",
+ eventP->data.gadgetEnter.gadgetID,
+ (unsigned long) eventP->data.gadgetEnter.gadgetP);
+ break;
+
+ case frmGadgetMiscEvent:
+ sprintf(eventText,"frmGadgetMiscEvent ID:%u, gadget:0x%08lX, selector:%u",
+ eventP->data.gadgetMisc.gadgetID,
+ (unsigned long) eventP->data.gadgetMisc.gadgetP,
+ eventP->data.gadgetMisc.selector);
+ break;
+
+ default:
+ if (eventP->eType >= firstINetLibEvent && eventP->eType < firstWebLibEvent)
+ {
+ sprintf(eventText, "NetLib event #%u", eventP->eType);
+ }
+ else if (eventP->eType >= firstWebLibEvent && eventP->eType < firstWebLibEvent + 0x0100)
+ {
+ sprintf(eventText, "WebLib event #%u", eventP->eType);
+ }
+ else if (eventP->eType >= firstUserEvent)
+ {
+ sprintf(eventText, "Application event #%u", eventP->eType);
+ }
+ else
+ {
+ sprintf(eventText,"Unknown Event! Event->eType #: %u",
+ eventP->eType);
+ }
+ break;
+ }
+
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+// ¥ LogEvtAddEventToQueue
+// ---------------------------------------------------------------------------
+
+void LogEvtAddEventToQueue (const EventType& event)
+{
+ if (LogEnqueuedEvents ())
+ {
+ // Get the text for this event. If there is such text, log it.
+
+ char eventText[kEventTextMaxLen] = " -> EvtAddEventToQueue: ";
+ if (PrvGetEventText (&event, eventText))
+ {
+ LogAppendMsg (eventText);
+ }
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ LogEvtAddUniqueEventToQueue
+// ---------------------------------------------------------------------------
+
+void LogEvtAddUniqueEventToQueue (const EventType& event, UInt32, Boolean)
+{
+ if (LogEnqueuedEvents ())
+ {
+ // Get the text for this event. If there is such text, log it.
+
+ char eventText[kEventTextMaxLen] = " -> EvtAddUniqueEventToQueue: ";
+ if (PrvGetEventText (&event, eventText))
+ {
+ LogAppendMsg (eventText);
+ }
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ LogEvtEnqueueKey
+// ---------------------------------------------------------------------------
+
+void LogEvtEnqueueKey (UInt16 ascii, UInt16 keycode, UInt16 modifiers)
+{
+ if (LogEnqueuedEvents ())
+ {
+ if ((ascii < 0x0100) && isprint (ascii))
+ {
+ LogAppendMsg (" -> EvtEnqueueKey: ascii = '%c' 0x%04X, keycode = 0x%04X, modifiers = 0x%04X.",
+ (char) ascii, ascii, keycode, modifiers);
+ }
+ else
+ {
+ LogAppendMsg (" -> EvtEnqueueKey: ascii = 0x%04X, keycode = 0x%04X, modifiers = 0x%04X.",
+ ascii, keycode, modifiers);
+ }
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ LogEvtEnqueuePenPoint
+// ---------------------------------------------------------------------------
+
+void LogEvtEnqueuePenPoint (const PointType& pt)
+{
+ if (LogEnqueuedEvents ())
+ {
+ LogAppendMsg (" -> EvtEnqueuePenPoint: pen->x=%d, pen->y=%d.", pt.x, pt.y);
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ LogEvtGetEvent
+// ---------------------------------------------------------------------------
+
+void LogEvtGetEvent (const EventType& event, Int32 timeout)
+{
+ UNUSED_PARAM(timeout)
+
+ if (LogDequeuedEvents ())
+ {
+ // Get the text for this event. If there is such text, log it.
+
+ char eventText[kEventTextMaxLen] = "<- EvtGetEvent: ";
+ if (PrvGetEventText (&event, eventText))
+ {
+ LogAppendMsg (eventText);
+ }
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ LogEvtGetPen
+// ---------------------------------------------------------------------------
+
+void LogEvtGetPen (Int16 screenX, Int16 screenY, Boolean penDown)
+{
+ if (LogDequeuedEvents ())
+ {
+ static Int16 lastScreenX = -2;
+ static Int16 lastScreenY = -2;
+ static Boolean lastPenDown = false;
+ static long numCollapsedEvents;
+
+ if (screenX != lastScreenX ||
+ screenY != lastScreenY ||
+ penDown != lastPenDown)
+ {
+ lastScreenX = screenX;
+ lastScreenY = screenY;
+ lastPenDown = penDown;
+
+ numCollapsedEvents = 0;
+
+ LogAppendMsg ("<- EvtGetPen: screenX=%d, screenY=%d, penDown=%d.",
+ (int) screenX, (int) screenY, (int) penDown);
+ }
+ else
+ {
+ ++numCollapsedEvents;
+ if (numCollapsedEvents == 1)
+ LogAppendMsg ("<- EvtGetPen: <<<eliding identical events>>>.");
+ }
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// ¥ LogEvtGetSysEvent
+// ---------------------------------------------------------------------------
+
+void LogEvtGetSysEvent (const EventType& event, Int32 timeout)
+{
+ UNUSED_PARAM(timeout)
+
+ if (LogDequeuedEvents ())
+ {
+ // Get the text for this event. If there is such text, log it.
+
+ char eventText[kEventTextMaxLen] = "<- EvtGetSysEvent: ";
+ if (PrvGetEventText (&event, eventText))
+ {
+ LogAppendMsg (eventText);
+ }
+ }
+}
+