/* logger.c * * Copyright 2025 Francois Techene * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later * * ---------------------------------------------------------------- * Utility library to log to a file or to the console. * * Before using the logger, you must initialize it by calling: * * initLogger(DEBUG_LEVEL, LOG_METHOD, LOG_FILE); * * Where those variables are defined in logger.h. * * You may put all logging code inbetween the USE_LOGGER condition * so that loggin logic can be easily fully disabled. * * #ifdef USE_LOGGER * initLogger(DEBUG_LEVEL, LOG_METHOD, LOG_FILE); * #endif * * #ifdef USE_LOGGER * logger.debug("message"); * #endif * * If you are logging to the console, you need to forward the * application's events to the logger and ignore the event if it * has been handled by the console: * * if(logger.onEvent(event)) { * return; * } * ---------------------------------------------------------------- */ #include #include #include #include #include "logger.h" void LogToNone(char *format, ...); Boolean DoNotIntercept(EventRecord event); #ifdef USE_LOGGER typedef struct _LogTextState { int font; int size; int mode; int face; } LogTextState; #define CONSOLE_BAR_SIZE 12 #define CONSOLE_TEXT_FONT monaco #define CONSOLE_TEXT_SIZE 9 #define CONSOLE_MIN_HEIGHT 67 #define CONSOLE_MIN_WIDTH 150 LogTextState logSavedTextState; PenState logSavedPentState; void UseLogger(LogLevel level, LogOutputMethod out, char *filePath); void LogConsoleInit(void); void LogConsoleDraw(void); void LogConsoleUpdate(void); void LogConsoleInitScrollbar(void); void LogConsoleResetTE(void); void LogConsoleResetScroll(void); void LogConsoleScrollToBottom(void); void LogConsoleSetLayout(); void LogConsoleResize(Rect* newBounds); void LogConsoleZoom(void); void LogConsoleClear(void); void LogToConsole(char *format, ...); void LogToFile(char *format, ...); void LogDefault(char *format, ...); void LogDebug(char *format, ...); void LogInfo(char *format, ...); void LogWarn(char *format, ...); void LogError(char *format, ...); int DoLogGrow(Point p); pascal void DoLogScroll(ControlHandle ctl, short ctlEventIn); Boolean DoLogEvent(EventRecord event); void LogSaveTextState(void); void LogRestoreTextState(void); #endif Logger logger; void LogToNone(char* format, ...) { (void) format; // unused. return; } Boolean DoNotIntercept(EventRecord event) { return false; } void InitLogger(LogLevel level, LogOutputMethod out, char* filePath) { logger.print = LogToNone; logger.debug = LogToNone; logger.info = LogToNone; logger.warn = LogToNone; logger.error = LogToNone; logger.onEvent = DoNotIntercept; #ifdef USE_LOGGER UseLogger(level,out, filePath); #endif } #ifdef USE_LOGGER // Only load if required. void UseLogger(LogLevel level, LogOutputMethod out, char* filePath) { logger.log = LogDefault; logger.debug = LogDebug; logger.info = LogInfo; logger.warn = LogWarn; logger.error = LogError; logger.logLevel = level; logger.outputMethod = out; logger.filePath = filePath; logger.console.textArea = NULL; if (out == CONSOLE_OUT) { logger.onEvent = DoLogEvent; LogConsoleInit(); } else if (out == FILE_OUT) { FILE* file; // Using Unix New Line character for tail to // read correctly on Linux. // logger.newLine = "\x0A\r"; logger.print = LogToFile; // Empty log file. If the file cannot be created, the log output // is set to NONE_OUT; // file = fopen(logger.filePath, "w"); if(!file) { logger.outputMethod = NONE_OUT; logger.print = LogToNone; return; } fprintf(file, ""); fclose(file); } else { logger.print = LogToNone; } } void LogConsoleInit(void) { GrafPtr oldPort; Rect winBounds; Rect viewRect; Rect textRect; logger.console.lineHeight = CONSOLE_TEXT_SIZE + 2; logger.console.width = screenBits.bounds.right - 2; logger.console.scrollPos = 0; logger.console.maxScrollPos = 0; logger.console.nbLines = 0; logger.console.contentHeight = 0; logger.console.nbOutputLines = CONSOLE_LINES; logger.console.height = (logger.console.lineHeight * CONSOLE_LINES) + (CONSOLE_BAR_SIZE * 2) - 1; // Make prevBounds to the zoomed in position for zoom toggle // logger.console.growBounds.left = screenBits.bounds.left + 5; logger.console.growBounds.right = screenBits.bounds.right - 5; logger.console.growBounds.top = screenBits.bounds.top + 25; logger.console.growBounds.bottom = screenBits.bounds.bottom - 5; logger.console.prevBounds = logger.console.growBounds; logger.newLine = "\r"; logger.print = LogToConsole; winBounds.left = screenBits.bounds.left + 1; winBounds.right = screenBits.bounds.right - 1; winBounds.bottom = screenBits.bounds.bottom - 1; winBounds.top = winBounds.bottom - logger.console.height; logger.console.window = NewWindow(0L, &winBounds, "\pConsole", TRUE, plainDBox, (WindowPtr)-1L, TRUE, 0); if (!logger.console.window) { logger.print = LogToNone; return; } logger.console.origBounds = winBounds; GetPort(&oldPort); SetPort(logger.console.window); SendBehind(logger.console.window, NULL); LogConsoleSetLayout(); // Tex Edit // viewRect = logger.console.view; //InsetRect(&viewRect, 2, 2); textRect = viewRect; InsetRect(&textRect, 2, 0); logger.console.textArea = TENew(&textRect, &viewRect); (*(logger.console.textArea))->txFont = CONSOLE_TEXT_FONT; (*(logger.console.textArea))->txSize = CONSOLE_TEXT_SIZE; (*(logger.console.textArea))->lineHeight = logger.console.lineHeight; (*(logger.console.textArea))->fontAscent = logger.console.lineHeight - 2; (*(logger.console.textArea))->txFace = 0; //TEActivate(logger.console.textArea); LogConsoleInitScrollbar(); LogConsoleDraw(); SetPort(oldPort); } void LogConsoleDraw(void) { Rect r; Pattern tbarPattern; GrafPtr oldPort; GetPort(&oldPort); SetPort(logger.console.window); LogSaveTextState(); GetPenState(&logSavedPentState); PenNormal(); EraseRect(&logger.console.view); GetIndPattern(&tbarPattern, sysPatListID, 24); FillRect(&logger.console.titleBar, tbarPattern); FrameRect(&logger.console.titleBar); FillRect(&logger.console.bottomBar, white); FrameRect(&logger.console.bottomBar); FillRect(&logger.console.growBtn, white); FrameRect(&logger.console.growBtn); r = logger.console.growBtn; InsetRect(&r, 5, 3); OffsetRect(&r, 1, 1); FrameRect(&r); r.right -= 1; r.bottom -= 1; OffsetRect(&r, -2, -2); FillRect(&r, white); FrameRect(&r); // Lower button // FillRect(&logger.console.lowerBtn, white); FrameRect(&logger.console.lowerBtn); MoveTo(logger.console.lowerBtn.left + 4, logger.console.lowerBtn.bottom - 2); TextFont(geneva); TextFace(0); TextSize(12); DrawString("\p_"); // Clear button // FillRect(&logger.console.clearBtn, white); FrameRect(&logger.console.clearBtn); MoveTo(logger.console.clearBtn.left + 4, logger.console.clearBtn.bottom - 2); TextFont(geneva); TextFace(0); TextSize(8); DrawString("\pclear"); // Close button // FillRect(&logger.console.closeBtn, white); FrameRect(&logger.console.closeBtn); // Expand button // FillRect(&logger.console.zoomBtn, white); FrameRect(&logger.console.zoomBtn); LogConsoleUpdate(); LogRestoreTextState(); SetPenState(&logSavedPentState); SetPort(oldPort); } void LogConsoleUpdate(void) { TEUpdate(&logger.console.view, logger.console.textArea); DrawControls(logger.console.window); } void LogConsoleInitScrollbar(void) { GrafPtr oldPort; Rect scrollbarRect; int value; GetPort(&oldPort); SetPort(logger.console.window); scrollbarRect.left = logger.console.window->portRect.right - 15; scrollbarRect.right = logger.console.window->portRect.right + 1; scrollbarRect.top = logger.console.titleBar.bottom - 1; scrollbarRect.bottom = logger.console.bottomBar.top + 1; value = logger.console.contentHeight - logger.console.height; logger.console.vScroll = NewControl(logger.console.window, &scrollbarRect, "\p", TRUE, 0, // intinial value; 0, // min value, // max scrollBarProc, 0); SetCtlMax(logger.console.vScroll, logger.console.contentHeight - logger.console.height); SetCtlValue(logger.console.vScroll, logger.console.scrollPos); HiliteControl(logger.console.vScroll, 255); SetPort(oldPort); } void LogConsoleResetTE(void) { Rect viewRect = logger.console.view; //InsetRect(&viewRect, 2, 2); // Resize the size of the text area // (*(logger.console.textArea))->viewRect = viewRect; (*(logger.console.textArea))->destRect.right = viewRect.right - 1; } void LogConsoleResetScroll(void) { int oldMaxScrollPos = logger.console.maxScrollPos; logger.console.maxScrollPos = (logger.console.nbLines - logger.console.nbOutputLines) * logger.console.lineHeight; if (logger.console.maxScrollPos <= 0) { // If there is nothing to scroll, // put everything back at the top // TEScroll(0, logger.console.scrollPos, logger.console.textArea); logger.console.scrollPos = 0; logger.console.maxScrollPos = 0; } else if (logger.console.scrollPos > logger.console.maxScrollPos) { // Window is growing // if (logger.console.maxScrollPos < oldMaxScrollPos) { int delta = logger.console.scrollPos - logger.console.maxScrollPos; TEScroll(0, delta, logger.console.textArea); } logger.console.scrollPos = logger.console.maxScrollPos; } else { HiliteControl(logger.console.vScroll, 0); } MoveControl(logger.console.vScroll, logger.console.bounds.right - 15, logger.console.titleBar.bottom - 1); SizeControl(logger.console.vScroll, 16, logger.console.bottomBar.top - logger.console.titleBar.bottom + 2); SetCtlMax(logger.console.vScroll, logger.console.maxScrollPos); SetCtlValue(logger.console.vScroll, logger.console.scrollPos); } void LogConsoleScrollToBottom(void) { int delta = logger.console.maxScrollPos - logger.console.scrollPos; logger.console.scrollPos = logger.console.maxScrollPos; SetCtlValue(logger.console.vScroll, logger.console.scrollPos); TEScroll(0, -delta, logger.console.textArea); LogConsoleUpdate(); } void LogConsoleSetLayout(void) { logger.console.bounds = logger.console.window->portRect; logger.console.view = logger.console.bounds; logger.console.view.right -= 15; logger.console.view.top += CONSOLE_BAR_SIZE; logger.console.view.bottom -= CONSOLE_BAR_SIZE - 1; // Title bar & buttons // logger.console.titleBar.left = logger.console.bounds.left - 1; logger.console.titleBar.right = logger.console.bounds.right + 1; logger.console.titleBar.top = logger.console.bounds.top - 1; logger.console.titleBar.bottom = logger.console.titleBar.top + CONSOLE_BAR_SIZE + 1; logger.console.bottomBar.left = logger.console.bounds.left - 1; logger.console.bottomBar.right = logger.console.bounds.right + 1; logger.console.bottomBar.top = logger.console.bounds.bottom - CONSOLE_BAR_SIZE +1; logger.console.bottomBar.bottom = logger.console.bounds.bottom + 1; logger.console.zoomBtn.right = logger.console.bounds.right - 5; logger.console.zoomBtn.left = logger.console.zoomBtn.right - 9; logger.console.zoomBtn.top = logger.console.bounds.top + 1; logger.console.zoomBtn.bottom = logger.console.zoomBtn.top + 9; logger.console.growBtn.right = logger.console.bounds.right + 1; logger.console.growBtn.left = logger.console.growBtn.right - 16; logger.console.growBtn.bottom = logger.console.bounds.bottom + 1; logger.console.growBtn.top = logger.console.growBtn.bottom - CONSOLE_BAR_SIZE; logger.console.lowerBtn.right = logger.console.zoomBtn.left - 10; logger.console.lowerBtn.left = logger.console.lowerBtn.right - 16; logger.console.lowerBtn.top = logger.console.bounds.top + 1; logger.console.lowerBtn.bottom = logger.console.lowerBtn.top + 9; logger.console.clearBtn.right = logger.console.lowerBtn.left -4; logger.console.clearBtn.left = logger.console.clearBtn.right - 26; logger.console.clearBtn.top = logger.console.bounds.top + 1; logger.console.clearBtn.bottom = logger.console.clearBtn.top + 9; logger.console.closeBtn.left = logger.console.bounds.left + 5; logger.console.closeBtn.right = logger.console.closeBtn.left + 9; logger.console.closeBtn.top = logger.console.bounds.top + 1; logger.console.closeBtn.bottom = logger.console.closeBtn.top + 9; } void LogConsoleResize(Rect* newBounds) { int width = newBounds->right - newBounds->left; int height = newBounds->bottom - newBounds->top; int nbLines = (int)((height - (CONSOLE_BAR_SIZE * 2) + 1) / logger.console.lineHeight); // Adjust the hieight of the window to match the number of // lines and line height of the TE // logger.console.nbOutputLines = nbLines; logger.console.width = width; logger.console.height = (logger.console.lineHeight * nbLines) + (CONSOLE_BAR_SIZE * 2) - 1; newBounds->bottom = newBounds->top + logger.console.height; MoveWindow(logger.console.window, newBounds->left, newBounds->top, TRUE); SizeWindow(logger.console.window, logger.console.width, logger.console.height, FALSE); EraseRect(newBounds); LogConsoleSetLayout(); LogConsoleResetTE(); LogConsoleResetScroll(); LogConsoleDraw(); //LogConsoleScrollToBottom(); } void LogConsoleZoom(void) { Point topLeft; Point bottomRight; topLeft.h = logger.console.window->portRect.left; topLeft.v = logger.console.window->portRect.top; bottomRight.h = logger.console.window->portRect.right; bottomRight.v = logger.console.window->portRect.bottom; LocalToGlobal(&topLeft); LocalToGlobal(&bottomRight); LogConsoleResize(&logger.console.prevBounds); logger.console.prevBounds.left = topLeft.h; logger.console.prevBounds.top = topLeft.v; logger.console.prevBounds.right = bottomRight.h; logger.console.prevBounds.bottom = bottomRight.v; } void LogConsoleClear(void) { GrafPtr oldPort; Rect scrollbarRect; int value; GetPort(&oldPort); SetPort(logger.console.window); // Move back to the top of the text area // TEScroll(0, logger.console.scrollPos, logger.console.textArea); // Select all and erase // TESetSelect(0,32767,logger.console.textArea); TEDelete(logger.console.textArea); logger.console.scrollPos = 0; logger.console.maxScrollPos = 0; logger.console.nbLines = 0; logger.console.contentHeight = 0; SetCtlMax(logger.console.vScroll, 0); SetCtlValue(logger.console.vScroll, 0); HiliteControl(logger.console.vScroll, 255); LogConsoleUpdate(); SetPort(oldPort); } void LogToConsole(char* format, ...) { va_list ap; char log[256] = ""; int oldMaxScroll = logger.console.maxScrollPos; GrafPtr oldPort; GetPort(&oldPort); SetPort(logger.console.window); va_start(ap, format); vsprintf(log, format, ap); va_end(ap); TEInsert(log, strlen(log), logger.console.textArea); // Update scroll data // logger.console.nbLines = (*(logger.console.textArea))->nLines; logger.console.contentHeight = logger.console.nbLines * logger.console.lineHeight; logger.console.maxScrollPos = (logger.console.nbLines - logger.console.nbOutputLines) * logger.console.lineHeight; SetCtlMax(logger.console.vScroll, logger.console.maxScrollPos); // Auto-scroll to bottom (do this only once) // if (logger.console.nbLines == logger.console.nbOutputLines + 1) { // Enable the scrollbar once it is needed // HiliteControl(logger.console.vScroll, 0); } if (logger.console.nbLines > logger.console.nbOutputLines && logger.console.scrollPos == oldMaxScroll) { LogConsoleScrollToBottom(); } SetPort(oldPort); ShowHide(logger.console.window, TRUE); } void LogToFile(char* format, ...) { FILE* file; va_list ap; // Open file in Append Binary mode so the // new line character is not converted. // file = fopen(logger.filePath, "ab"); if(!file) { return; } va_start(ap, format); vfprintf(file, format, ap); va_end(ap); fclose(file); } void LogDefault(char* format, ...) { va_list ap; char log[256] = ""; va_start(ap, format); vsprintf(log, format, ap); va_end(ap); logger.print("%s %s", log, logger.newLine); } void LogDebug(char* format, ...) { va_list ap; char log[256] = ""; if (logger.logLevel < DEBUG_LEVEL) { return; } va_start(ap, format); vsprintf(log, format, ap); va_end(ap); logger.print("DEBUG: %s %s", log, logger.newLine); } void LogInfo(char* format, ...) { va_list ap; char log[256] = ""; if (logger.logLevel < INFO_LEVEL) { return; } va_start(ap, format); vsprintf(log, format, ap); va_end(ap); logger.print("INFO: %s %s", log, logger.newLine); } void LogWarn(char* format, ...) { va_list ap; char log[256] = ""; if (logger.logLevel < WARNING_LEVEL) { return; } va_start(ap, format); vsprintf(log, format, ap); va_end(ap); logger.print("WARNING: %s %s", log, logger.newLine); } void LogError(char* format, ...) { va_list ap; char log[256] = ""; if (logger.logLevel < ERROR_LEVEL) { return; } va_start(ap, format); vsprintf(log, format, ap); va_end(ap); logger.print("ERROR: %s %s", log, logger.newLine); } int DoLogGrow(Point p) { Rect newBounds; GrafPtr savePort; Rect limits; long theResult; Point orig = {0, 0}; GetPort(&savePort); SetPort(logger.console.window); SetRect(&limits, CONSOLE_MIN_WIDTH, // Min horizontal size CONSOLE_MIN_HEIGHT, // Min vertical size screenBits.bounds.right, // Max horizontal size screenBits.bounds.bottom); // Max vertical size theResult = GrowWindow(logger.console.window, p, &limits); if (theResult == 0) { return 0; } LocalToGlobal(&orig); newBounds.left = orig.h; newBounds.right = newBounds.left + LoWord(theResult); newBounds.top = orig.v; newBounds.bottom = newBounds.top + HiWord(theResult); LogConsoleResize(&newBounds); logger.console.prevBounds = logger.console.growBounds; return 1; } pascal void DoLogScroll(ControlHandle ctl, short ctlEventIn) { int value = logger.console.scrollPos; int orig = value; (void) ctl; // unused switch (ctlEventIn) { case inUpButton: value -= logger.console.lineHeight; break; case inDownButton: value += logger.console.lineHeight; break; case inPageUp: value -= logger.console.lineHeight * (logger.console.nbOutputLines - 1); break; case inPageDown: value += logger.console.lineHeight * (logger.console.nbOutputLines - 1); break; } // Clamp // if (value < 0) { value = 0; } else if (value > logger.console.maxScrollPos) { value = logger.console.maxScrollPos; } if (value != orig) { logger.console.scrollPos = value; SetCtlValue(logger.console.vScroll, logger.console.scrollPos); TEScroll(0, -(value - orig), logger.console.textArea); LogConsoleUpdate(); } } Boolean DoLogEvent(EventRecord event) { Boolean interceped = FALSE; GrafPtr oldPort; WindowPtr eventWin; ControlHandle ctl; int eventIn, ctlEventIn; Point localPt; if (logger.outputMethod != CONSOLE_OUT) { return FALSE; } GetPort(&oldPort); SetPort(logger.console.window); localPt = event.where; GlobalToLocal(&localPt); switch (event.what) { case nullEvent: break; case mouseDown: eventIn = FindWindow(event.where, &eventWin); if (eventWin != logger.console.window) { interceped = FALSE; break; } SelectWindow(eventWin); switch (eventIn) { case inContent: ctlEventIn = FindControl(localPt, eventWin, &ctl); if (PtInRect(localPt, &logger.console.closeBtn)) { HideWindow(logger.console.window); interceped = TRUE; } else if (PtInRect(localPt, &logger.console.lowerBtn)) { Rect origBounds = logger.console.origBounds; logger.console.prevBounds = logger.console.growBounds; LogConsoleResize(&origBounds); LogConsoleScrollToBottom(); } else if (PtInRect(localPt, &logger.console.clearBtn)) { LogConsoleClear(); } else if (PtInRect(localPt, &logger.console.zoomBtn)) { LogConsoleZoom(); LogConsoleScrollToBottom(); } else if (PtInRect(localPt, &logger.console.titleBar)) { DragWindow(eventWin, event.where, &screenBits.bounds); InvalRect(&screenBits.bounds); } else if (PtInRect(localPt, &logger.console.growBtn)) { DoLogGrow(event.where); } else if (ctl == logger.console.vScroll) { if (ctlEventIn == inThumb) { int ctlValue; int delta; // Dragging the thumb // TrackControl(ctl, localPt, NULL); ctlValue = GetCtlValue(ctl); delta = ctlValue - logger.console.scrollPos; logger.console.scrollPos = ctlValue; TEScroll(0, -delta, logger.console.textArea); LogConsoleUpdate(); } else { // Buttons + page scroll // TrackControl(ctl, localPt, DoLogScroll); } } interceped = TRUE; break; case inGrow: logger.debug("In grow"); interceped = TRUE; break; case inGoAway: if (TrackGoAway(eventWin, event.where)) { HideWindow(logger.console.window); interceped = TRUE; } break; } break; case updateEvt: eventWin = (WindowPtr)event.message; if (eventWin && eventWin == logger.console.window) { BeginUpdate(eventWin); LogConsoleDraw(); EndUpdate(eventWin); } break; case activateEvt: break; } SetPort(oldPort); return interceped; } void LogSaveTextState(void) { logSavedTextState.font = thePort->txFont; logSavedTextState.size = thePort->txSize; logSavedTextState.mode = thePort->txMode; logSavedTextState.face = thePort->txFace; } void LogRestoreTextState(void) { TextFont(logSavedTextState.font); TextSize(logSavedTextState.size); TextMode(logSavedTextState.mode); TextFace(logSavedTextState.face); } #endif