The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 1 | // |
| 2 | // Copyright 2005 The Android Open Source Project |
| 3 | // |
| 4 | // Display runtime log output. |
| 5 | // |
| 6 | |
| 7 | // For compilers that support precompilation, include "wx/wx.h". |
| 8 | #include "wx/wxprec.h" |
| 9 | |
| 10 | // Otherwise, include all standard headers |
| 11 | #ifndef WX_PRECOMP |
| 12 | # include "wx/wx.h" |
| 13 | #endif |
| 14 | #include "wx/image.h" // needed for Windows build |
| 15 | #include "wx/dcbuffer.h" |
| 16 | |
| 17 | #include "LogWindow.h" |
| 18 | #include "LogMessage.h" |
| 19 | #include "LogPrefsDialog.h" |
| 20 | #include "MyApp.h" |
| 21 | #include "Preferences.h" |
| 22 | #include "Resource.h" |
| 23 | #include "UserEventMessage.h" |
| 24 | |
| 25 | #include <errno.h> |
| 26 | |
| 27 | static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...); |
| 28 | static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args); |
| 29 | |
| 30 | |
| 31 | using namespace android; |
| 32 | |
| 33 | #if 0 // experiment -- works on Win32, but not with GTK |
| 34 | class MyTextCtrl : public wxTextCtrl { |
| 35 | public: |
| 36 | MyTextCtrl(wxWindow* parent, wxWindowID id, const wxString& value, |
| 37 | const wxPoint& pos, const wxSize& size, int style = 0) |
| 38 | : wxTextCtrl(parent, id, value, pos, size, style) |
| 39 | { |
| 40 | printf("***************** MyTextCtrl!\n"); |
| 41 | } |
| 42 | |
| 43 | void OnScroll(wxScrollWinEvent& event); |
| 44 | void OnScrollBottom(wxScrollWinEvent& event); |
| 45 | |
| 46 | private: |
| 47 | DECLARE_EVENT_TABLE() |
| 48 | }; |
| 49 | |
| 50 | BEGIN_EVENT_TABLE(MyTextCtrl, wxTextCtrl) |
| 51 | EVT_SCROLLWIN(MyTextCtrl::OnScroll) |
| 52 | EVT_SCROLLWIN_BOTTOM(MyTextCtrl::OnScrollBottom) |
| 53 | END_EVENT_TABLE() |
| 54 | |
| 55 | void MyTextCtrl::OnScroll(wxScrollWinEvent& event) |
| 56 | { |
| 57 | printf("OnScroll!\n"); |
| 58 | } |
| 59 | |
| 60 | void MyTextCtrl::OnScrollBottom(wxScrollWinEvent& event) |
| 61 | { |
| 62 | printf("OnScrollBottom!\n"); |
| 63 | } |
| 64 | #endif |
| 65 | |
| 66 | |
| 67 | BEGIN_EVENT_TABLE(LogWindow, wxDialog) |
| 68 | EVT_CLOSE(LogWindow::OnClose) |
| 69 | EVT_MOVE(LogWindow::OnMove) |
| 70 | EVT_COMBOBOX(IDC_LOG_LEVEL, LogWindow::OnLogLevel) |
| 71 | EVT_BUTTON(IDC_LOG_CLEAR, LogWindow::OnLogClear) |
| 72 | EVT_BUTTON(IDC_LOG_PAUSE, LogWindow::OnLogPause) |
| 73 | EVT_BUTTON(IDC_LOG_PREFS, LogWindow::OnLogPrefs) |
| 74 | END_EVENT_TABLE() |
| 75 | |
| 76 | /* |
| 77 | * Information about log levels. |
| 78 | * |
| 79 | * Each entry here corresponds to an entry in the combo box. The first |
| 80 | * letter of each name should be unique. |
| 81 | */ |
| 82 | static const struct { |
| 83 | wxString name; |
| 84 | android_LogPriority priority; |
| 85 | } gLogLevels[] = { |
| 86 | { wxT("Verbose"), ANDROID_LOG_VERBOSE }, |
| 87 | { wxT("Debug"), ANDROID_LOG_DEBUG }, |
| 88 | { wxT("Info"), ANDROID_LOG_INFO }, |
| 89 | { wxT("Warn"), ANDROID_LOG_WARN }, |
| 90 | { wxT("Error"), ANDROID_LOG_ERROR } |
| 91 | }; |
| 92 | |
| 93 | |
| 94 | /* |
| 95 | * Create a new LogWindow. This should be a child of the main frame. |
| 96 | */ |
| 97 | LogWindow::LogWindow(wxWindow* parent) |
| 98 | : wxDialog(parent, wxID_ANY, wxT("Log Output"), wxDefaultPosition, |
| 99 | wxDefaultSize, |
| 100 | wxCAPTION | wxSYSTEM_MENU | wxCLOSE_BOX | wxRESIZE_BORDER), |
| 101 | mDisplayArray(NULL), mMaxDisplayMsgs(0), mPaused(false), |
| 102 | mMinPriority(ANDROID_LOG_VERBOSE), |
| 103 | mHeaderFormat(LogPrefsDialog::kHFFull), |
| 104 | mSingleLine(false), mExtraSpacing(0), mPointSize(10), mUseColor(true), |
| 105 | mFontMonospace(true), mWriteFile(false), mTruncateOld(true), mLogFp(NULL), |
| 106 | mNewlyShown(false), mLastPosition(wxDefaultPosition), mVisible(false) |
| 107 | { |
| 108 | ConstructControls(); |
| 109 | |
| 110 | Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); |
| 111 | |
| 112 | int poolSize = 10240; // 10MB |
| 113 | pPrefs->GetInt("log-pool-size-kbytes", &poolSize); |
| 114 | assert(poolSize > 0); |
| 115 | mPool.Resize(poolSize * 1024); |
| 116 | |
| 117 | mMaxDisplayMsgs = 1000; |
| 118 | pPrefs->GetInt("log-display-msg-count", &mMaxDisplayMsgs); |
| 119 | assert(mMaxDisplayMsgs > 0); |
| 120 | mDisplayArray = new LogMessage*[mMaxDisplayMsgs]; |
| 121 | memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs); |
| 122 | mTopPtr = -1; |
| 123 | mNextPtr = 0; |
| 124 | |
| 125 | int tmpInt = (int) mHeaderFormat; |
| 126 | pPrefs->GetInt("log-header-format", &tmpInt); |
| 127 | mHeaderFormat = (LogPrefsDialog::HeaderFormat) tmpInt; |
| 128 | pPrefs->GetBool("log-single-line", &mSingleLine); |
| 129 | pPrefs->GetInt("log-extra-spacing", &mExtraSpacing); |
| 130 | pPrefs->GetInt("log-point-size", &mPointSize); |
| 131 | pPrefs->GetBool("log-use-color", &mUseColor); |
| 132 | pPrefs->SetBool("log-font-monospace", &mFontMonospace); |
| 133 | SetTextStyle(); |
| 134 | |
| 135 | mFileName = wxT("/tmp/android-log.txt"); |
| 136 | pPrefs->GetBool("log-write-file", &mWriteFile); |
| 137 | pPrefs->GetString("log-filename", /*ref*/mFileName); |
| 138 | pPrefs->GetBool("log-truncate-old", &mTruncateOld); |
| 139 | |
| 140 | PrepareLogFile(); |
| 141 | } |
| 142 | |
| 143 | /* |
| 144 | * Destroy everything we own. |
| 145 | */ |
| 146 | LogWindow::~LogWindow(void) |
| 147 | { |
| 148 | ClearDisplay(); |
| 149 | delete[] mDisplayArray; |
| 150 | |
| 151 | if (mLogFp != NULL) |
| 152 | fclose(mLogFp); |
| 153 | } |
| 154 | |
| 155 | /* |
| 156 | * Set the text style, based on our preferences. |
| 157 | */ |
| 158 | void LogWindow::SetTextStyle(void) |
| 159 | { |
| 160 | wxTextCtrl* pTextCtrl; |
| 161 | pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); |
| 162 | wxTextAttr style; |
| 163 | style = pTextCtrl->GetDefaultStyle(); |
| 164 | |
| 165 | if (mFontMonospace) { |
| 166 | wxFont font(mPointSize, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, |
| 167 | wxFONTWEIGHT_NORMAL); |
| 168 | style.SetFont(font); |
| 169 | } else { |
| 170 | wxFont font(mPointSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, |
| 171 | wxFONTWEIGHT_NORMAL); |
| 172 | style.SetFont(font); |
| 173 | } |
| 174 | |
| 175 | pTextCtrl->SetDefaultStyle(style); |
| 176 | } |
| 177 | |
| 178 | /* |
| 179 | * Set up the goodies in the window. |
| 180 | * |
| 181 | * Also initializes mMinPriority. |
| 182 | */ |
| 183 | void LogWindow::ConstructControls(void) |
| 184 | { |
| 185 | Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); |
| 186 | wxPanel* base = new wxPanel(this, wxID_ANY); |
| 187 | wxBoxSizer* masterSizer = new wxBoxSizer(wxVERTICAL); |
| 188 | wxBoxSizer* indentSizer = new wxBoxSizer(wxHORIZONTAL); |
| 189 | wxBoxSizer* configPrioritySizer = new wxBoxSizer(wxHORIZONTAL); |
| 190 | wxGridSizer* configSizer = new wxGridSizer(4, 1); |
| 191 | |
| 192 | /* |
| 193 | * Configure log level combo box. |
| 194 | */ |
| 195 | wxComboBox* logLevel; |
| 196 | int defaultLogLevel = 1; |
| 197 | pPrefs->GetInt("log-display-level", &defaultLogLevel); |
| 198 | logLevel = new wxComboBox(base, IDC_LOG_LEVEL, wxT(""), |
| 199 | wxDefaultPosition, wxDefaultSize, 0, NULL, |
| 200 | wxCB_READONLY /*| wxSUNKEN_BORDER*/); |
| 201 | for (int i = 0; i < NELEM(gLogLevels); i++) { |
| 202 | logLevel->Append(gLogLevels[i].name); |
| 203 | logLevel->SetClientData(i, (void*) gLogLevels[i].priority); |
| 204 | } |
| 205 | logLevel->SetSelection(defaultLogLevel); |
| 206 | mMinPriority = gLogLevels[defaultLogLevel].priority; |
| 207 | |
| 208 | /* |
| 209 | * Set up stuff at the bottom, starting with the options |
| 210 | * at the bottom left. |
| 211 | */ |
| 212 | configPrioritySizer->Add(new wxStaticText(base, wxID_ANY, wxT("Log level:"), |
| 213 | wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT), |
| 214 | 0, wxALIGN_CENTER_VERTICAL); |
| 215 | configPrioritySizer->AddSpacer(kInterSpacing); |
| 216 | configPrioritySizer->Add(logLevel); |
| 217 | |
| 218 | wxButton* clear = new wxButton(base, IDC_LOG_CLEAR, wxT("&Clear"), |
| 219 | wxDefaultPosition, wxDefaultSize, 0); |
| 220 | wxButton* pause = new wxButton(base, IDC_LOG_PAUSE, wxT("&Pause"), |
| 221 | wxDefaultPosition, wxDefaultSize, 0); |
| 222 | wxButton* prefs = new wxButton(base, IDC_LOG_PREFS, wxT("C&onfigure"), |
| 223 | wxDefaultPosition, wxDefaultSize, 0); |
| 224 | |
| 225 | configSizer->Add(configPrioritySizer, 0, wxALIGN_LEFT); |
| 226 | configSizer->Add(clear, 0, wxALIGN_CENTER); |
| 227 | configSizer->Add(pause, 0, wxALIGN_CENTER); |
| 228 | configSizer->Add(prefs, 0, wxALIGN_RIGHT); |
| 229 | |
| 230 | /* |
| 231 | * Create text ctrl. |
| 232 | */ |
| 233 | wxTextCtrl* pTextCtrl; |
| 234 | pTextCtrl = new wxTextCtrl(base, IDC_LOG_TEXT, wxT(""), |
| 235 | wxDefaultPosition, wxDefaultSize, |
| 236 | wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2 | wxTE_NOHIDESEL | |
| 237 | wxHSCROLL); |
| 238 | |
| 239 | /* |
| 240 | * Add components to master sizer. |
| 241 | */ |
| 242 | masterSizer->AddSpacer(kEdgeSpacing); |
| 243 | masterSizer->Add(pTextCtrl, 1, wxEXPAND); |
| 244 | masterSizer->AddSpacer(kInterSpacing); |
| 245 | masterSizer->Add(configSizer, 0, wxEXPAND); |
| 246 | masterSizer->AddSpacer(kEdgeSpacing); |
| 247 | |
| 248 | /* |
| 249 | * Indent from sides. |
| 250 | */ |
| 251 | indentSizer->AddSpacer(kEdgeSpacing); |
| 252 | indentSizer->Add(masterSizer, 1, wxEXPAND); |
| 253 | indentSizer->AddSpacer(kEdgeSpacing); |
| 254 | |
| 255 | base->SetSizer(indentSizer); |
| 256 | |
| 257 | indentSizer->Fit(this); // shrink-to-fit |
| 258 | indentSizer->SetSizeHints(this); // define minimum size |
| 259 | } |
| 260 | |
| 261 | /* |
| 262 | * In some cases, this means the user has clicked on our "close" button. |
| 263 | * We don't really even want one, but both WinXP and KDE put one on our |
| 264 | * window whether we want it or not. So, we make it work as a "hide" |
| 265 | * button instead. |
| 266 | * |
| 267 | * This also gets called when the app is shutting down, and we do want |
| 268 | * to destroy ourselves then, saving various information about our state. |
| 269 | */ |
| 270 | void LogWindow::OnClose(wxCloseEvent& event) |
| 271 | { |
| 272 | /* just hide the window, unless we're shutting down */ |
| 273 | if (event.CanVeto()) { |
| 274 | event.Veto(); |
| 275 | Show(false); |
| 276 | return; |
| 277 | } |
| 278 | |
| 279 | /* |
| 280 | * Save some preferences. |
| 281 | */ |
| 282 | SaveWindowPrefs(); |
| 283 | |
| 284 | /* if we can't veto the Close(), destroy ourselves */ |
| 285 | Destroy(); |
| 286 | } |
| 287 | |
| 288 | /* |
| 289 | * Save all of our preferences to the config file. |
| 290 | */ |
| 291 | void LogWindow::SaveWindowPrefs(void) |
| 292 | { |
| 293 | Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); |
| 294 | |
| 295 | /* |
| 296 | * Save shown/hidden state. |
| 297 | */ |
| 298 | pPrefs->SetBool("window-log-show", IsShown()); |
| 299 | |
| 300 | /* |
| 301 | * Limits and formatting prefs. |
| 302 | */ |
| 303 | pPrefs->SetInt("log-display-msg-count", mMaxDisplayMsgs); |
| 304 | pPrefs->SetInt("log-pool-size-kbytes", mPool.GetMaxSize() / 1024); |
| 305 | |
| 306 | pPrefs->SetInt("log-header-format", mHeaderFormat); |
| 307 | pPrefs->SetBool("log-single-line", mSingleLine); |
| 308 | pPrefs->SetInt("log-extra-spacing", mExtraSpacing); |
| 309 | pPrefs->SetInt("log-point-size", mPointSize); |
| 310 | pPrefs->SetBool("log-use-color", mUseColor); |
| 311 | pPrefs->SetBool("log-font-monospace", mFontMonospace); |
| 312 | |
| 313 | pPrefs->SetBool("log-write-file", mWriteFile); |
| 314 | pPrefs->SetString("log-filename", mFileName.ToAscii()); |
| 315 | pPrefs->SetBool("log-truncate-old", mTruncateOld); |
| 316 | |
| 317 | /* |
| 318 | * Save window size and position. |
| 319 | */ |
| 320 | wxPoint posn; |
| 321 | wxSize size; |
| 322 | |
| 323 | assert(pPrefs != NULL); |
| 324 | |
| 325 | posn = GetPosition(); |
| 326 | size = GetSize(); |
| 327 | |
| 328 | pPrefs->SetInt("window-log-x", posn.x); |
| 329 | pPrefs->SetInt("window-log-y", posn.y); |
| 330 | pPrefs->SetInt("window-log-width", size.GetWidth()); |
| 331 | pPrefs->SetInt("window-log-height", size.GetHeight()); |
| 332 | |
| 333 | /* |
| 334 | * Save current setting of debug level combo box. |
| 335 | */ |
| 336 | wxComboBox* pCombo; |
| 337 | int selection; |
| 338 | pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL); |
| 339 | selection = pCombo->GetSelection(); |
| 340 | pPrefs->SetInt("log-display-level", selection); |
| 341 | } |
| 342 | |
| 343 | /* |
| 344 | * Return the desired position and size. |
| 345 | */ |
| 346 | /*static*/ wxRect LogWindow::GetPrefWindowRect(void) |
| 347 | { |
| 348 | Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); |
| 349 | int x, y, width, height; |
| 350 | |
| 351 | assert(pPrefs != NULL); |
| 352 | |
| 353 | x = y = 10; |
| 354 | width = 500; |
| 355 | height = 200; |
| 356 | |
| 357 | /* these don't modify the arg if the pref doesn't exist */ |
| 358 | pPrefs->GetInt("window-log-x", &x); |
| 359 | pPrefs->GetInt("window-log-y", &y); |
| 360 | pPrefs->GetInt("window-log-width", &width); |
| 361 | pPrefs->GetInt("window-log-height", &height); |
| 362 | |
| 363 | return wxRect(x, y, width, height); |
| 364 | } |
| 365 | |
| 366 | /* |
| 367 | * Under Linux+GTK, the first time you show the window, it appears where |
| 368 | * it's supposed to. If you then hide it and show it again, it gets |
| 369 | * moved on top of the parent window. After that, you can reposition it |
| 370 | * and it remembers its position across hide/show. |
| 371 | * |
| 372 | * To counter this annoyance, we save the position when we hide, and |
| 373 | * reset the position after a show. The "newly shown" flag ensures that |
| 374 | * we only reposition the window as the result of a Show(true) call. |
| 375 | * |
| 376 | * Sometimes, something helpful will shift the window over if it's |
| 377 | * partially straddling a seam between two monitors. I don't see an easy |
| 378 | * way to block this, and I'm not sure I want to anyway. |
| 379 | */ |
| 380 | void LogWindow::OnMove(wxMoveEvent& event) |
| 381 | { |
| 382 | wxPoint point; |
| 383 | point = event.GetPosition(); |
| 384 | //printf("Sim: log window is at (%d,%d) (new=%d)\n", point.x, point.y, |
| 385 | // mNewlyShown); |
| 386 | |
| 387 | if (mNewlyShown) { |
| 388 | if (mLastPosition == wxDefaultPosition) { |
| 389 | //printf("Sim: no last position established\n"); |
| 390 | } else { |
| 391 | Move(mLastPosition); |
| 392 | } |
| 393 | |
| 394 | mNewlyShown = false; |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | /* |
| 399 | * Set the "newly shown" flag. |
| 400 | */ |
| 401 | bool LogWindow::Show(bool show) |
| 402 | { |
| 403 | if (show) { |
| 404 | mNewlyShown = true; |
| 405 | Redisplay(); |
| 406 | } else { |
| 407 | mLastPosition = GetPosition(); |
| 408 | } |
| 409 | |
| 410 | mVisible = show; |
| 411 | return wxDialog::Show(show); |
| 412 | } |
| 413 | |
| 414 | /* |
| 415 | * User has adjusted the log level. Update the display appropriately. |
| 416 | * |
| 417 | * This is a wxEVT_COMMAND_COMBOBOX_SELECTED event. |
| 418 | */ |
| 419 | void LogWindow::OnLogLevel(wxCommandEvent& event) |
| 420 | { |
| 421 | int selection; |
| 422 | android_LogPriority priority; |
| 423 | |
| 424 | selection = event.GetInt(); |
| 425 | wxComboBox* pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL); |
| 426 | priority = (android_LogPriority) (long)pCombo->GetClientData(event.GetInt()); |
| 427 | |
| 428 | printf("Sim: log level selected: %d (%s)\n", (int) priority, |
| 429 | (const char*) gLogLevels[selection].name.ToAscii()); |
| 430 | mMinPriority = priority; |
| 431 | Redisplay(); |
| 432 | } |
| 433 | |
| 434 | /* |
| 435 | * Clear out the log. |
| 436 | */ |
| 437 | void LogWindow::OnLogClear(wxCommandEvent& event) |
| 438 | { |
| 439 | ClearDisplay(); |
| 440 | mPool.Clear(); |
| 441 | } |
| 442 | |
| 443 | /* |
| 444 | * Handle the pause/resume button. |
| 445 | * |
| 446 | * If we're un-pausing, we need to get caught up. |
| 447 | */ |
| 448 | void LogWindow::OnLogPause(wxCommandEvent& event) |
| 449 | { |
| 450 | mPaused = !mPaused; |
| 451 | |
| 452 | wxButton* pButton = (wxButton*) FindWindow(IDC_LOG_PAUSE); |
| 453 | if (mPaused) { |
| 454 | pButton->SetLabel(wxT("&Resume")); |
| 455 | |
| 456 | mPool.SetBookmark(); |
| 457 | } else { |
| 458 | pButton->SetLabel(wxT("&Pause")); |
| 459 | |
| 460 | LogMessage* pMsg = mPool.GetBookmark(); |
| 461 | if (pMsg == NULL) { |
| 462 | /* bookmarked item fell out of pool */ |
| 463 | printf("--- bookmark was lost, redisplaying\n"); |
| 464 | Redisplay(); |
| 465 | } else { |
| 466 | /* |
| 467 | * The bookmark points to the last item added to the display. |
| 468 | * We want to chase its "prev" pointer to walk toward the head |
| 469 | * of the list, adding items from oldest to newest. |
| 470 | */ |
| 471 | pMsg = pMsg->GetPrev(); |
| 472 | while (pMsg != NULL) { |
| 473 | if (FilterMatches(pMsg)) |
| 474 | AddToDisplay(pMsg); |
| 475 | pMsg = pMsg->GetPrev(); |
| 476 | } |
| 477 | } |
| 478 | } |
| 479 | } |
| 480 | |
| 481 | /* |
| 482 | * Open log preferences dialog. |
| 483 | */ |
| 484 | void LogWindow::OnLogPrefs(wxCommandEvent& event) |
| 485 | { |
| 486 | LogPrefsDialog dialog(this); |
| 487 | |
| 488 | /* |
| 489 | * Set up the dialog. |
| 490 | */ |
| 491 | dialog.mHeaderFormat = mHeaderFormat; |
| 492 | dialog.mSingleLine = mSingleLine; |
| 493 | dialog.mExtraSpacing = mExtraSpacing; |
| 494 | dialog.mPointSize = mPointSize; |
| 495 | dialog.mUseColor = mUseColor; |
| 496 | dialog.mFontMonospace = mFontMonospace; |
| 497 | |
| 498 | dialog.mDisplayMax = mMaxDisplayMsgs; |
| 499 | dialog.mPoolSizeKB = mPool.GetMaxSize() / 1024; |
| 500 | |
| 501 | dialog.mWriteFile = mWriteFile; |
| 502 | dialog.mFileName = mFileName; |
| 503 | dialog.mTruncateOld = mTruncateOld; |
| 504 | |
| 505 | /* |
| 506 | * Show it. If they hit "OK", copy the updated values out, and |
| 507 | * re-display the log output. |
| 508 | */ |
| 509 | if (dialog.ShowModal() == wxID_OK) { |
| 510 | /* discard old display arra */ |
| 511 | ClearDisplay(); |
| 512 | delete[] mDisplayArray; |
| 513 | |
| 514 | mHeaderFormat = dialog.mHeaderFormat; |
| 515 | mSingleLine = dialog.mSingleLine; |
| 516 | mExtraSpacing = dialog.mExtraSpacing; |
| 517 | mPointSize = dialog.mPointSize; |
| 518 | mUseColor = dialog.mUseColor; |
| 519 | mFontMonospace = dialog.mFontMonospace; |
| 520 | |
| 521 | assert(dialog.mDisplayMax > 0); |
| 522 | assert(dialog.mPoolSizeKB > 0); |
| 523 | mMaxDisplayMsgs = dialog.mDisplayMax; |
| 524 | mPool.Resize(dialog.mPoolSizeKB * 1024); |
| 525 | |
| 526 | mWriteFile = dialog.mWriteFile; |
| 527 | if (mLogFp != NULL && mFileName != dialog.mFileName) { |
| 528 | printf("--- log file name changed, closing\n"); |
| 529 | fclose(mLogFp); |
| 530 | mLogFp = NULL; |
| 531 | } |
| 532 | mFileName = dialog.mFileName; |
| 533 | mTruncateOld = dialog.mTruncateOld; |
| 534 | |
| 535 | mDisplayArray = new LogMessage*[mMaxDisplayMsgs]; |
| 536 | memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs); |
| 537 | Redisplay(); |
| 538 | |
| 539 | PrepareLogFile(); |
| 540 | } |
| 541 | } |
| 542 | |
| 543 | /* |
| 544 | * Handle a log message "user event". This should only be called in |
| 545 | * the main UI thread. |
| 546 | * |
| 547 | * We take ownership of "*pLogMessage". |
| 548 | */ |
| 549 | void LogWindow::AddLogMessage(LogMessage* pLogMessage) |
| 550 | { |
| 551 | mPool.Add(pLogMessage); |
| 552 | |
| 553 | if (!mPaused && mVisible && FilterMatches(pLogMessage)) { |
| 554 | /* |
| 555 | * Thought: keep a reference to the previous message. If it |
| 556 | * matches in most fields (all except timestamp?), hold it and |
| 557 | * increment a counter. If we get a message that doesn't match, |
| 558 | * or a timer elapses, synthesize a "previous message repeated N |
| 559 | * times" string. |
| 560 | */ |
| 561 | AddToDisplay(pLogMessage); |
| 562 | } |
| 563 | |
| 564 | // release the initial ref caused by allocation |
| 565 | pLogMessage->Release(); |
| 566 | |
| 567 | if (mLogFp != NULL) |
| 568 | LogToFile(pLogMessage); |
| 569 | } |
| 570 | |
| 571 | /* |
| 572 | * Clear out the display, releasing any log messages held in the display |
| 573 | * array. |
| 574 | */ |
| 575 | void LogWindow::ClearDisplay(void) |
| 576 | { |
| 577 | wxTextCtrl* pTextCtrl; |
| 578 | pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); |
| 579 | pTextCtrl->Clear(); |
| 580 | |
| 581 | /* |
| 582 | * Just run through the entire array. |
| 583 | */ |
| 584 | for (int i = 0; i < mMaxDisplayMsgs; i++) { |
| 585 | if (mDisplayArray[i] != NULL) { |
| 586 | mDisplayArray[i]->Release(); |
| 587 | mDisplayArray[i] = NULL; |
| 588 | } |
| 589 | } |
| 590 | mTopPtr = -1; |
| 591 | mNextPtr = 0; |
| 592 | } |
| 593 | |
| 594 | /* |
| 595 | * Clear the current display and regenerate it from the log pool. We need |
| 596 | * to do this whenever we change filters or log message formatting. |
| 597 | */ |
| 598 | void LogWindow::Redisplay(void) |
| 599 | { |
| 600 | /* |
| 601 | * Freeze output rendering so it doesn't flash during update. Doesn't |
| 602 | * seem to help for GTK, and it leaves garbage on the screen in WinXP, |
| 603 | * so I'm leaving it commented out. |
| 604 | */ |
| 605 | //wxTextCtrl* pText = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); |
| 606 | //pText->Freeze(); |
| 607 | |
| 608 | //printf("--- redisplay\n"); |
| 609 | ClearDisplay(); |
| 610 | |
| 611 | /* |
| 612 | * Set up the default wxWidgets text style stuff. |
| 613 | */ |
| 614 | SetTextStyle(); |
| 615 | |
| 616 | /* |
| 617 | * Here's the plan: |
| 618 | * - Start at the head of the pool (where the most recently added |
| 619 | * items are). |
| 620 | * - Check to see if the current item passes our filter. If it does, |
| 621 | * increment the "found count". |
| 622 | * - Continue in this manner until we run out of pool or have |
| 623 | * sufficient items to fill the screen. |
| 624 | * - Starting from the current position, walk back toward the head, |
| 625 | * adding the items that meet the current filter criteria. |
| 626 | * |
| 627 | * Don't forget that the log pool could be empty. |
| 628 | */ |
| 629 | LogMessage* pMsg = mPool.GetHead(); |
| 630 | |
| 631 | if (pMsg != NULL) { |
| 632 | int foundCount = 0; |
| 633 | |
| 634 | // note this stops before it runs off the end |
| 635 | while (pMsg->GetNext() != NULL && foundCount < mMaxDisplayMsgs) { |
| 636 | if (FilterMatches(pMsg)) |
| 637 | foundCount++; |
| 638 | pMsg = pMsg->GetNext(); |
| 639 | } |
| 640 | |
| 641 | while (pMsg != NULL) { |
| 642 | if (FilterMatches(pMsg)) |
| 643 | AddToDisplay(pMsg); |
| 644 | pMsg = pMsg->GetPrev(); |
| 645 | } |
| 646 | } |
| 647 | |
| 648 | //pText->Thaw(); |
| 649 | } |
| 650 | |
| 651 | |
| 652 | /* |
| 653 | * Returns "true" if the currently specified filters would allow this |
| 654 | * message to be shown. |
| 655 | */ |
| 656 | bool LogWindow::FilterMatches(const LogMessage* pLogMessage) |
| 657 | { |
| 658 | if (pLogMessage->GetPriority() >= mMinPriority) |
| 659 | return true; |
| 660 | else |
| 661 | return false; |
| 662 | } |
| 663 | |
| 664 | /* |
| 665 | * Realloc the array of pointers, and remove anything from the display |
| 666 | * that should no longer be there. |
| 667 | */ |
| 668 | void LogWindow::SetMaxDisplayMsgs(int max) |
| 669 | { |
| 670 | Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); |
| 671 | |
| 672 | pPrefs->SetInt("log-display-msg-count", max); |
| 673 | } |
| 674 | |
| 675 | /* |
| 676 | * Add the message to the display array and to the screen. |
| 677 | */ |
| 678 | void LogWindow::AddToDisplay(LogMessage* pLogMessage) |
| 679 | { |
| 680 | wxTextCtrl* pTextCtrl; |
| 681 | pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); |
| 682 | |
| 683 | if (mNextPtr == mTopPtr) { |
| 684 | /* |
| 685 | * The display array is full. |
| 686 | * |
| 687 | * We need to eliminate the topmost entry. This requires removing |
| 688 | * it from the array and removing the text from the wxTextCtrl. |
| 689 | */ |
| 690 | pTextCtrl->Remove(0, mDisplayArray[mTopPtr]->GetTextCtrlLen()); |
| 691 | mDisplayArray[mTopPtr]->Release(); |
| 692 | mTopPtr = (mTopPtr + 1) % mMaxDisplayMsgs; |
| 693 | } |
| 694 | |
| 695 | /* |
| 696 | * Add formatted text to the text ctrl. Track how much actual space |
| 697 | * is required. The space may be different on Win32 (CRLF-based) vs. |
| 698 | * GTK (LF-based), so we need to measure it, not compute it from the |
| 699 | * text string. |
| 700 | */ |
| 701 | long lastBefore, lastAfter; |
| 702 | //long insertBefore; |
| 703 | //insertBefore = pTextCtrl->GetInsertionPoint(); |
| 704 | lastBefore = pTextCtrl->GetLastPosition(); |
| 705 | FormatMessage(pLogMessage, pTextCtrl); |
| 706 | lastAfter = pTextCtrl->GetLastPosition(); |
| 707 | pLogMessage->SetTextCtrlLen(lastAfter - lastBefore); |
| 708 | |
| 709 | /* |
| 710 | * If we restore the old insertion point, we will be glued to where |
| 711 | * we were. This is okay until we start deleting text from the top, |
| 712 | * at which point we need to adjust it to retain our position. |
| 713 | * |
| 714 | * If we set the insertion point to the bottom, we effectively |
| 715 | * implement "scroll to bottom on output". |
| 716 | * |
| 717 | * If we don't set it at all, we get slightly strange behavior out |
| 718 | * of GTK, which seems to be par for the course here. |
| 719 | */ |
| 720 | //pTextCtrl->SetInsertionPoint(insertBefore); // restore insertion pt |
| 721 | pTextCtrl->SetInsertionPoint(lastAfter); |
| 722 | |
| 723 | /* add it to array, claim ownership */ |
| 724 | mDisplayArray[mNextPtr] = pLogMessage; |
| 725 | pLogMessage->Acquire(); |
| 726 | |
| 727 | /* adjust pointers */ |
| 728 | if (mTopPtr < 0) // first time only |
| 729 | mTopPtr = 0; |
| 730 | mNextPtr = (mNextPtr + 1) % mMaxDisplayMsgs; |
| 731 | } |
| 732 | |
| 733 | |
| 734 | /* |
| 735 | * Return a human-readable string for the priority level. Always returns |
| 736 | * a valid string. |
| 737 | */ |
| 738 | static const wxCharBuffer GetPriorityString(android_LogPriority priority) |
| 739 | { |
| 740 | int idx; |
| 741 | |
| 742 | idx = (int) priority - (int) ANDROID_LOG_VERBOSE; |
| 743 | if (idx < 0 || idx >= NELEM(gLogLevels)) |
| 744 | return "?unknown?"; |
| 745 | return gLogLevels[idx].name.ToAscii(); |
| 746 | } |
| 747 | |
| 748 | /* |
| 749 | * Format a message and write it to the text control. |
| 750 | */ |
| 751 | void LogWindow::FormatMessage(const LogMessage* pLogMessage, |
| 752 | wxTextCtrl* pTextCtrl) |
| 753 | { |
| 754 | #if defined(HAVE_LOCALTIME_R) |
| 755 | struct tm tmBuf; |
| 756 | #endif |
| 757 | struct tm* ptm; |
| 758 | char timeBuf[32]; |
| 759 | char msgBuf[256]; |
| 760 | int msgLen = 0; |
| 761 | char* outBuf; |
| 762 | char priChar; |
| 763 | LogPrefsDialog::HeaderFormat headerFmt; |
| 764 | |
| 765 | headerFmt = mHeaderFormat; |
| 766 | if (pLogMessage->GetInternal()) |
| 767 | headerFmt = LogPrefsDialog::kHFInternal; |
| 768 | |
| 769 | priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0]; |
| 770 | |
| 771 | /* |
| 772 | * Get the current date/time in pretty form |
| 773 | * |
| 774 | * It's often useful when examining a log with "less" to jump to |
| 775 | * a specific point in the file by searching for the date/time stamp. |
| 776 | * For this reason it's very annoying to have regexp meta characters |
| 777 | * in the time stamp. Don't use forward slashes, parenthesis, |
| 778 | * brackets, asterisks, or other special chars here. |
| 779 | */ |
| 780 | time_t when = pLogMessage->GetWhen(); |
| 781 | const char* fmt = NULL; |
| 782 | #if defined(HAVE_LOCALTIME_R) |
| 783 | ptm = localtime_r(&when, &tmBuf); |
| 784 | #else |
| 785 | ptm = localtime(&when); |
| 786 | #endif |
| 787 | switch (headerFmt) { |
| 788 | case LogPrefsDialog::kHFFull: |
| 789 | case LogPrefsDialog::kHFInternal: |
| 790 | fmt = "%m-%d %H:%M:%S"; |
| 791 | break; |
| 792 | case LogPrefsDialog::kHFBrief: |
| 793 | case LogPrefsDialog::kHFMinimal: |
| 794 | fmt = "%H:%M:%S"; |
| 795 | break; |
| 796 | default: |
| 797 | break; |
| 798 | } |
| 799 | if (fmt != NULL) |
| 800 | strftime(timeBuf, sizeof(timeBuf), fmt, ptm); |
| 801 | else |
| 802 | strcpy(timeBuf, "-"); |
| 803 | |
| 804 | const int kMaxExtraNewlines = 2; |
| 805 | char hdrNewline[2]; |
| 806 | char finalNewlines[kMaxExtraNewlines+1 +1]; |
| 807 | |
| 808 | if (mSingleLine) |
| 809 | hdrNewline[0] = ' '; |
| 810 | else |
| 811 | hdrNewline[0] = '\n'; |
| 812 | hdrNewline[1] = '\0'; |
| 813 | |
| 814 | assert(mExtraSpacing <= kMaxExtraNewlines); |
| 815 | int i; |
| 816 | for (i = 0; i < mExtraSpacing+1; i++) |
| 817 | finalNewlines[i] = '\n'; |
| 818 | finalNewlines[i] = '\0'; |
| 819 | |
| 820 | wxTextAttr msgColor; |
| 821 | switch (pLogMessage->GetPriority()) { |
| 822 | case ANDROID_LOG_WARN: |
| 823 | msgColor.SetTextColour(*wxBLUE); |
| 824 | break; |
| 825 | case ANDROID_LOG_ERROR: |
| 826 | msgColor.SetTextColour(*wxRED); |
| 827 | break; |
| 828 | case ANDROID_LOG_VERBOSE: |
| 829 | case ANDROID_LOG_DEBUG: |
| 830 | case ANDROID_LOG_INFO: |
| 831 | default: |
| 832 | msgColor.SetTextColour(*wxBLACK); |
| 833 | break; |
| 834 | } |
| 835 | if (pLogMessage->GetInternal()) |
| 836 | msgColor.SetTextColour(*wxGREEN); |
| 837 | |
| 838 | /* |
| 839 | * Construct a buffer containing the log header. |
| 840 | */ |
| 841 | bool splitHeader = true; |
| 842 | outBuf = msgBuf; |
| 843 | switch (headerFmt) { |
| 844 | case LogPrefsDialog::kHFFull: |
| 845 | splitHeader = true; |
| 846 | msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| 847 | "[ %s %5d %c/%-6.6s]%s", |
| 848 | timeBuf, pLogMessage->GetPid(), priChar, |
| 849 | pLogMessage->GetTag(), hdrNewline); |
| 850 | break; |
| 851 | case LogPrefsDialog::kHFBrief: |
| 852 | splitHeader = true; |
| 853 | msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| 854 | "[%s %5d]%s", |
| 855 | timeBuf, pLogMessage->GetPid(), hdrNewline); |
| 856 | break; |
| 857 | case LogPrefsDialog::kHFMinimal: |
| 858 | splitHeader = false; |
| 859 | msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| 860 | "%s %5d- %s", |
| 861 | timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg()); |
| 862 | break; |
| 863 | case LogPrefsDialog::kHFInternal: |
| 864 | splitHeader = false; |
| 865 | msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| 866 | "[%s] %s", timeBuf, pLogMessage->GetMsg()); |
| 867 | break; |
| 868 | default: |
| 869 | fprintf(stderr, "Sim: unexpected header format %d\n", headerFmt); |
| 870 | assert(false); |
| 871 | break; |
| 872 | } |
| 873 | |
| 874 | if (msgLen < 0) { |
| 875 | fprintf(stderr, "WHOOPS\n"); |
| 876 | assert(outBuf == msgBuf); |
| 877 | return; |
| 878 | } |
| 879 | |
| 880 | if (splitHeader) { |
| 881 | if (mUseColor) |
| 882 | pTextCtrl->SetDefaultStyle(wxTextAttr(*wxLIGHT_GREY)); |
| 883 | pTextCtrl->AppendText(wxString::FromAscii(outBuf)); |
| 884 | if (mUseColor) |
| 885 | pTextCtrl->SetDefaultStyle(msgColor); |
| 886 | pTextCtrl->AppendText(wxString::FromAscii(pLogMessage->GetMsg())); |
| 887 | if (mUseColor) |
| 888 | pTextCtrl->SetDefaultStyle(*wxBLACK); |
| 889 | pTextCtrl->AppendText(wxString::FromAscii(finalNewlines)); |
| 890 | } else { |
| 891 | if (mUseColor) |
| 892 | pTextCtrl->SetDefaultStyle(msgColor); |
| 893 | pTextCtrl->AppendText(wxString::FromAscii(outBuf)); |
| 894 | if (mUseColor) |
| 895 | pTextCtrl->SetDefaultStyle(*wxBLACK); |
| 896 | pTextCtrl->AppendText(wxString::FromAscii(finalNewlines)); |
| 897 | } |
| 898 | |
| 899 | /* if we allocated storage for this message, free it */ |
| 900 | if (outBuf != msgBuf) |
| 901 | free(outBuf); |
| 902 | } |
| 903 | |
| 904 | /* |
| 905 | * Write the message to the log file. |
| 906 | * |
| 907 | * We can't just do this in FormatMessage(), because that re-writes all |
| 908 | * messages on the display whenever the output format or filter changes. |
| 909 | * |
| 910 | * Use a one-log-per-line format here to make "grep" useful. |
| 911 | */ |
| 912 | void LogWindow::LogToFile(const LogMessage* pLogMessage) |
| 913 | { |
| 914 | #if defined(HAVE_LOCALTIME_R) |
| 915 | struct tm tmBuf; |
| 916 | #endif |
| 917 | struct tm* ptm; |
| 918 | char timeBuf[32]; |
| 919 | char msgBuf[256]; |
| 920 | int msgLen; |
| 921 | char* outBuf; |
| 922 | char priChar; |
| 923 | |
| 924 | assert(mLogFp != NULL); |
| 925 | |
| 926 | time_t when = pLogMessage->GetWhen(); |
| 927 | #if defined(HAVE_LOCALTIME_R) |
| 928 | ptm = localtime_r(&when, &tmBuf); |
| 929 | #else |
| 930 | ptm = localtime(&when); |
| 931 | #endif |
| 932 | |
| 933 | strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm); |
| 934 | priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0]; |
| 935 | |
| 936 | outBuf = msgBuf; |
| 937 | if (pLogMessage->GetInternal()) { |
| 938 | msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| 939 | "[%s %5d *] %s\n", |
| 940 | timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg()); |
| 941 | } else { |
| 942 | msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| 943 | "[%s %5d %c] %s)\n", |
| 944 | timeBuf, pLogMessage->GetPid(), priChar, |
| 945 | pLogMessage->GetMsg()); |
| 946 | } |
| 947 | if (fwrite(outBuf, msgLen, 1, mLogFp) != 1) |
| 948 | fprintf(stderr, "Sim: WARNING: partial log write\n"); |
| 949 | fflush(mLogFp); |
| 950 | |
| 951 | /* if we allocated storage for this message, free it */ |
| 952 | if (outBuf != msgBuf) |
| 953 | free(outBuf); |
| 954 | } |
| 955 | |
| 956 | /* |
| 957 | * Get the modification date of a file. |
| 958 | */ |
| 959 | static bool GetFileModDate(const char* fileName, time_t* pModWhen) |
| 960 | { |
| 961 | struct stat sb; |
| 962 | |
| 963 | if (stat(fileName, &sb) < 0) |
| 964 | return false; |
| 965 | |
| 966 | *pModWhen = sb.st_mtime; |
| 967 | return true; |
| 968 | } |
| 969 | |
| 970 | /* |
| 971 | * Open or close the log file as appropriate. |
| 972 | */ |
| 973 | void LogWindow::PrepareLogFile(void) |
| 974 | { |
| 975 | const int kLogFileMaxAge = 8 * 60 * 60; // 8 hours |
| 976 | |
| 977 | if (!mWriteFile && mLogFp != NULL) { |
| 978 | printf("Sim: closing log file\n"); |
| 979 | fclose(mLogFp); |
| 980 | mLogFp = NULL; |
| 981 | } else if (mWriteFile && mLogFp == NULL) { |
| 982 | printf("Sim: opening log file '%s'\n", (const char*)mFileName.ToAscii()); |
| 983 | time_t now, modWhen = 0; |
| 984 | const char* openFlags; |
| 985 | |
| 986 | now = time(NULL); |
| 987 | if (!mTruncateOld || |
| 988 | (GetFileModDate(mFileName.ToAscii(), &modWhen) && |
| 989 | modWhen + kLogFileMaxAge > now)) |
| 990 | { |
| 991 | if (modWhen != 0) { |
| 992 | printf("--- log file is %.3f hours old, appending\n", |
| 993 | (now - modWhen) / 3600.0); |
| 994 | } |
| 995 | openFlags = "a"; // open for append (text mode) |
| 996 | } else { |
| 997 | if (modWhen != 0) { |
| 998 | printf("--- log file is %.3f hours old, truncating\n", |
| 999 | (now - modWhen) / 3600.0); |
| 1000 | } |
| 1001 | openFlags = "w"; // open for writing, truncate (text mode) |
| 1002 | } |
| 1003 | |
| 1004 | mLogFp = fopen(mFileName.ToAscii(), openFlags); |
| 1005 | if (mLogFp == NULL) { |
| 1006 | fprintf(stderr, "Sim: failed opening log file '%s': %s\n", |
| 1007 | (const char*) mFileName.ToAscii(), strerror(errno)); |
| 1008 | } else { |
| 1009 | fprintf(mLogFp, "\n\n"); |
| 1010 | fflush(mLogFp); |
| 1011 | } |
| 1012 | } |
| 1013 | } |
| 1014 | |
| 1015 | /* |
| 1016 | * Add a new log message. |
| 1017 | * |
| 1018 | * This function can be called from any thread. It makes a copy of the |
| 1019 | * stuff in "*pBundle" and sends it to the main UI thread. |
| 1020 | */ |
| 1021 | /*static*/ void LogWindow::PostLogMsg(const android_LogBundle* pBundle) |
| 1022 | { |
| 1023 | LogMessage* pNewMessage = LogMessage::Create(pBundle); |
| 1024 | |
| 1025 | SendToWindow(pNewMessage); |
| 1026 | } |
| 1027 | |
| 1028 | /* |
| 1029 | * Post a simple string to the log. |
| 1030 | */ |
| 1031 | /*static*/ void LogWindow::PostLogMsg(const char* msg) |
| 1032 | { |
| 1033 | LogMessage* pNewMessage = LogMessage::Create(msg); |
| 1034 | |
| 1035 | SendToWindow(pNewMessage); |
| 1036 | } |
| 1037 | |
| 1038 | /* |
| 1039 | * Post a simple wxString to the log. |
| 1040 | */ |
| 1041 | /*static*/ void LogWindow::PostLogMsg(const wxString& msg) |
| 1042 | { |
| 1043 | LogMessage* pNewMessage = LogMessage::Create(msg.ToAscii()); |
| 1044 | |
| 1045 | SendToWindow(pNewMessage); |
| 1046 | } |
| 1047 | |
| 1048 | /* |
| 1049 | * Send a log message to the log window. |
| 1050 | */ |
| 1051 | /*static*/ void LogWindow::SendToWindow(LogMessage* pMessage) |
| 1052 | { |
| 1053 | if (pMessage != NULL) { |
| 1054 | wxWindow* pMainFrame = ((MyApp*)wxTheApp)->GetMainFrame(); |
| 1055 | UserEventMessage* pUem = new UserEventMessage; |
| 1056 | pUem->CreateLogMessage(pMessage); |
| 1057 | |
| 1058 | UserEvent uev(0, (void*) pUem); |
| 1059 | |
| 1060 | pMainFrame->AddPendingEvent(uev); |
| 1061 | } else { |
| 1062 | fprintf(stderr, "Sim: failed to add new log message\n"); |
| 1063 | } |
| 1064 | } |
| 1065 | |
| 1066 | |
| 1067 | /* |
| 1068 | * This is a sanity check. We need to stop somewhere to avoid trashing |
| 1069 | * the system on bad input. |
| 1070 | */ |
| 1071 | #define kMaxLen 65536 |
| 1072 | |
| 1073 | #define VSNPRINTF vsnprintf // used to worry about _vsnprintf |
| 1074 | |
| 1075 | |
| 1076 | /* |
| 1077 | * Print a formatted message into a buffer. Pass in a buffer to try to use. |
| 1078 | * |
| 1079 | * If the buffer isn't big enough to hold the message, allocate storage |
| 1080 | * with malloc() and return that instead. The caller is responsible for |
| 1081 | * freeing the storage. |
| 1082 | * |
| 1083 | * Returns the length of the string, or -1 if the printf call failed. |
| 1084 | */ |
| 1085 | static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args) |
| 1086 | { |
| 1087 | int charsOut; |
| 1088 | char* localBuf = NULL; |
| 1089 | |
| 1090 | assert(pBuf != NULL && *pBuf != NULL); |
| 1091 | assert(bufLen > 0); |
| 1092 | assert(format != NULL); |
| 1093 | |
| 1094 | while (1) { |
| 1095 | /* |
| 1096 | * In some versions of libc, vsnprintf only returns 0 or -1, where |
| 1097 | * -1 indicates the the buffer wasn't big enough. In glibc 2.1 |
| 1098 | * and later, it returns the actual size needed. |
| 1099 | * |
| 1100 | * MinGW is just returning -1, so we have to retry there. |
| 1101 | */ |
| 1102 | char* newBuf; |
| 1103 | |
| 1104 | charsOut = VSNPRINTF(*pBuf, bufLen, format, args); |
| 1105 | |
| 1106 | if (charsOut >= 0 && charsOut < bufLen) |
| 1107 | break; |
| 1108 | |
| 1109 | //fprintf(stderr, "EXCEED: %d vs %d\n", charsOut, bufLen); |
| 1110 | if (charsOut < 0) { |
| 1111 | /* exact size not known, double previous size */ |
| 1112 | bufLen *= 2; |
| 1113 | if (bufLen > kMaxLen) |
| 1114 | goto fail; |
| 1115 | } else { |
| 1116 | /* exact size known, just use that */ |
| 1117 | |
| 1118 | bufLen = charsOut + 1; |
| 1119 | } |
| 1120 | //fprintf(stderr, "RETRY at %d\n", bufLen); |
| 1121 | |
| 1122 | newBuf = (char*) realloc(localBuf, bufLen); |
| 1123 | if (newBuf == NULL) |
| 1124 | goto fail; |
| 1125 | *pBuf = localBuf = newBuf; |
| 1126 | } |
| 1127 | |
| 1128 | // On platforms where snprintf() doesn't return the number of |
| 1129 | // characters output, we would need to call strlen() here. |
| 1130 | |
| 1131 | return charsOut; |
| 1132 | |
| 1133 | fail: |
| 1134 | if (localBuf != NULL) { |
| 1135 | free(localBuf); |
| 1136 | *pBuf = NULL; |
| 1137 | } |
| 1138 | return -1; |
| 1139 | } |
| 1140 | |
| 1141 | /* |
| 1142 | * Variable-arg form of the above. |
| 1143 | */ |
| 1144 | static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...) |
| 1145 | { |
| 1146 | va_list args; |
| 1147 | int result; |
| 1148 | |
| 1149 | va_start(args, format); |
| 1150 | result = android_vsnprintfBuffer(pBuf, bufLen, format, args); |
| 1151 | va_end(args); |
| 1152 | |
| 1153 | return result; |
| 1154 | } |
| 1155 | |
| 1156 | |