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 1157