Download
jcs
/subtext
/logger.c
(View History)
jcs logger: Shorten 'No one connected' to '0 connected' | Latest amendment: 475 on 2023-04-10 |
1 | /* |
2 | * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <stdio.h> |
18 | #include <stdarg.h> |
19 | #include <string.h> |
20 | #include "focusable.h" |
21 | #include "logger.h" |
22 | #include "subtext.h" |
23 | #include "user.h" |
24 | #include "util.h" |
25 | |
26 | struct logger *logger = NULL; |
27 | |
28 | void logger_layout(Rect *init_bounds); |
29 | void logger_key_down(struct focusable *focusable, EventRecord *event); |
30 | void logger_mouse_down(struct focusable *focusable, EventRecord *event); |
31 | void logger_resize(struct focusable *focusable, EventRecord *event); |
32 | void logger_update(struct focusable *focusable, EventRecord *event); |
33 | void logger_resume(struct focusable *focusable, EventRecord *event); |
34 | bool logger_quit(struct focusable *focusable); |
35 | void logger_flush_buffer(bool force); |
36 | |
37 | void |
38 | logger_init(void) |
39 | { |
40 | struct focusable *focusable; |
41 | Rect bounds = { 0 }; |
42 | short padding = 5; |
43 | |
44 | logger = xmalloczero(sizeof(struct logger)); |
45 | if (!logger) |
46 | panic("Can't allocate logger"); |
47 | logger->buffered_logs = xmalloc(LOGGER_BUFFERED_LOG_SIZE); |
48 | if (!logger->buffered_logs) |
49 | panic("Can't allocate logger buffer"); |
50 | logger->buffered_logs[0] = '\0'; |
51 | logger->autoflush = true; |
52 | |
53 | bounds.left = padding; |
54 | bounds.top = ((screenBits.bounds.bottom - |
55 | screenBits.bounds.top) / 3); |
56 | bounds.right = screenBits.bounds.right - padding - 1; |
57 | bounds.bottom = screenBits.bounds.bottom - padding - 1; |
58 | |
59 | logger->win = NewWindow(0L, &bounds, "\p", false, |
60 | documentProc, (WindowPtr)-1L, false, 0); |
61 | if (!logger->win) |
62 | panic("Can't create logger window"); |
63 | logger_update_title(); |
64 | |
65 | SetPort(logger->win); |
66 | |
67 | TextFont(LOGGER_FONT); |
68 | TextSize(LOGGER_FONT_SIZE); |
69 | |
70 | bounds.right -= bounds.left; |
71 | bounds.bottom -= bounds.top; |
72 | bounds.top = bounds.left = 0; |
73 | logger_layout(&bounds); |
74 | |
75 | focusable = xmalloczero(sizeof(struct focusable)); |
76 | if (!focusable) |
77 | panic("Can't create focusable"); |
78 | focusable->win = logger->win; |
79 | focusable->cookie = logger; |
80 | focusable->mouse_down = logger_mouse_down; |
81 | focusable->resize = logger_resize; |
82 | focusable->update = logger_update; |
83 | focusable->quit = logger_quit; |
84 | focusable->resume = logger_resume; |
85 | if (!add_focusable(focusable)) |
86 | panic("Can't add focusable"); |
87 | logger->focusable = focusable; |
88 | |
89 | DrawControls(logger->win); |
90 | DrawGrowIconOnly(logger->win); |
91 | } |
92 | |
93 | void |
94 | logger_layout(Rect *init_bounds) |
95 | { |
96 | Rect bounds, inset_bounds, win_bounds; |
97 | bool init = (init_bounds != NULL); |
98 | |
99 | if (init) |
100 | win_bounds = *init_bounds; |
101 | else |
102 | win_bounds = logger->win->portRect; |
103 | |
104 | /* messages scrollbar */ |
105 | bounds.top = -1; |
106 | bounds.right = win_bounds.right - win_bounds.left + 1; |
107 | bounds.bottom = win_bounds.bottom - win_bounds.top - 14; |
108 | bounds.left = bounds.right - SCROLLBAR_WIDTH; |
109 | if (init) { |
110 | logger->messages_scroller = NewControl(logger->win, &bounds, |
111 | "\p", true, 1, 1, 1, scrollBarProc, 0L); |
112 | if (!logger->messages_scroller) |
113 | panic("Can't create messages scroller"); |
114 | } else |
115 | (*(logger->messages_scroller))->contrlRect = bounds; |
116 | |
117 | /* messages */ |
118 | bounds.right = (*(logger->messages_scroller))->contrlRect.left; |
119 | bounds.left = 0; |
120 | bounds.top = 0; |
121 | bounds.bottom = win_bounds.bottom - win_bounds.top; |
122 | if (init) { |
123 | inset_bounds = bounds; |
124 | InsetRect(&inset_bounds, 4, 4); |
125 | logger->messages_te = TEStylNew(&inset_bounds, &bounds); |
126 | if (logger->messages_te == NULL) |
127 | panic("Can't create logger TE"); |
128 | (*(logger->messages_te))->caretHook = NullCaretHook; |
129 | TEActivate(logger->messages_te); |
130 | } else { |
131 | (*(logger->messages_te))->viewRect = bounds; |
132 | InsetRect(&bounds, 4, 4); |
133 | (*(logger->messages_te))->destRect = bounds; |
134 | TECalText(logger->messages_te); |
135 | TEUpdate(&(*(logger->messages_te))->viewRect, logger->messages_te); |
136 | } |
137 | |
138 | DrawControls(logger->win); |
139 | DrawGrowIconOnly(logger->win); |
140 | |
141 | if (!init) |
142 | logger_flush_buffer(true); |
143 | } |
144 | |
145 | void |
146 | logger_resume(struct focusable *focusable, EventRecord *event) |
147 | { |
148 | show_focusable(focusable); |
149 | InvalRect(logger->win->visRgn); |
150 | } |
151 | |
152 | bool |
153 | logger_quit(struct focusable *focusable) |
154 | { |
155 | destroy_focusable(focusable); |
156 | xfree(&logger->buffered_logs); |
157 | xfree(&logger); |
158 | |
159 | return true; |
160 | } |
161 | |
162 | void |
163 | logger_update(struct focusable *focusable, EventRecord *event) |
164 | { |
165 | GrafPtr old_port; |
166 | Rect r; |
167 | short what = -1; |
168 | |
169 | GetPort(&old_port); |
170 | SetPort(logger->win); |
171 | |
172 | if (event != NULL) |
173 | what = event->what; |
174 | |
175 | switch (what) { |
176 | case -1: |
177 | case updateEvt: |
178 | TextFont(applFont); |
179 | TextSize(10); |
180 | |
181 | EraseRect(&logger->win->portRect); |
182 | |
183 | r = (*(logger->messages_te))->viewRect; |
184 | InsetRect(&r, -1, -1); |
185 | FrameRect(&r); |
186 | |
187 | TEUpdate(&(*(logger->messages_te))->viewRect, logger->messages_te); |
188 | DrawControls(logger->win); |
189 | DrawGrowIconOnly(logger->win); |
190 | break; |
191 | case activateEvt: |
192 | if (event->modifiers & activeFlag) { |
193 | TEActivate(logger->messages_te); |
194 | } else { |
195 | TEDeactivate(logger->messages_te); |
196 | } |
197 | break; |
198 | } |
199 | |
200 | SetPort(old_port); |
201 | } |
202 | |
203 | void |
204 | logger_update_title(void) |
205 | { |
206 | static char logger_title[64]; |
207 | short n, uprinted = 0; |
208 | |
209 | snprintf(logger_title, sizeof(logger_title), |
210 | "%s | %luKB Free | %lu Call%s | ", db->config.name, |
211 | (FreeMem() / 1024), |
212 | session_today_tally.calls, session_today_tally.calls == 1 ? "" : "s"); |
213 | |
214 | for (n = 0; n < MAX_SESSIONS; n++) { |
215 | if (sessions[n] == NULL || !sessions[n]->logged_in) |
216 | continue; |
217 | |
218 | if (uprinted++) |
219 | strlcat(logger_title, ", ", sizeof(logger_title)); |
220 | else |
221 | strlcat(logger_title, "Logged in: ", sizeof(logger_title)); |
222 | |
223 | strlcat(logger_title, |
224 | sessions[n]->user ? sessions[n]->user->username : "guest", |
225 | sizeof(logger_title)); |
226 | } |
227 | |
228 | if (uprinted == 0) |
229 | strlcat(logger_title, "0 connected", sizeof(logger_title)); |
230 | |
231 | SetWTitle(logger->win, CtoPstr(logger_title)); |
232 | } |
233 | |
234 | void |
235 | logger_mouse_down(struct focusable *focusable, EventRecord *event) |
236 | { |
237 | Point p; |
238 | ControlHandle control; |
239 | Rect r; |
240 | int val, adj; |
241 | |
242 | p = event->where; |
243 | GlobalToLocal(&p); |
244 | |
245 | r = (*(logger->messages_te))->viewRect; |
246 | if (PtInRect(p, &r)) { |
247 | TEClick(p, ((event->modifiers & shiftKey) != 0), |
248 | logger->messages_te); |
249 | HLock(logger->messages_te); |
250 | if ((*(logger->messages_te))->selStart != |
251 | (*(logger->messages_te))->selEnd) |
252 | TESetSelect(0, 0, logger->messages_te); |
253 | HUnlock(logger->messages_te); |
254 | return; |
255 | } |
256 | |
257 | switch (FindControl(p, logger->win, &control)) { |
258 | case inUpButton: |
259 | case inDownButton: |
260 | case inPageUp: |
261 | case inPageDown: |
262 | if (control != logger->messages_scroller) |
263 | break; |
264 | SetTrackControlTE(logger->messages_te); |
265 | TrackControl(control, p, TrackMouseDownInControl); |
266 | break; |
267 | case inThumb: |
268 | val = GetCtlValue(control); |
269 | if (TrackControl(control, p, 0L) == 0) |
270 | break; |
271 | adj = val - GetCtlValue(control); |
272 | if (adj != 0) { |
273 | val -= adj; |
274 | if (control == logger->messages_scroller) |
275 | TEScroll(0, adj * TEGetHeight(0, 0, logger->messages_te), |
276 | logger->messages_te); |
277 | SetCtlValue(control, val); |
278 | } |
279 | break; |
280 | } |
281 | } |
282 | |
283 | void |
284 | logger_resize(struct focusable *focusable, EventRecord *event) |
285 | { |
286 | Rect bounds; |
287 | long newsize, width, height; |
288 | |
289 | bounds.left = 100; |
290 | bounds.top = 100; |
291 | bounds.right = screenBits.bounds.right; |
292 | bounds.bottom = screenBits.bounds.bottom; |
293 | |
294 | newsize = GrowWindow(focusable->win, event->where, &bounds); |
295 | |
296 | height = HiWord(newsize); |
297 | width = LoWord(newsize); |
298 | SizeWindow(focusable->win, width, height, true); |
299 | logger_layout(NULL); |
300 | } |
301 | |
302 | size_t |
303 | logger_printf(const char *format, ...) |
304 | { |
305 | va_list va; |
306 | size_t len; |
307 | |
308 | if (!logger) |
309 | return 0; |
310 | |
311 | va_start(va, format); |
312 | len = logger_vprintf(format, va); |
313 | va_end(va); |
314 | |
315 | return len; |
316 | } |
317 | |
318 | size_t |
319 | logger_vprintf(const char *format, va_list ap) |
320 | { |
321 | static char buf[600]; |
322 | ssize_t len; |
323 | time_t now = Time; |
324 | |
325 | if (!logger) |
326 | return 0; |
327 | |
328 | blanker_unblank(); |
329 | |
330 | len = strftime(buf, sizeof(buf), "\r[%H:%M:%S] ", localtime(&now)); |
331 | len += vsnprintf(buf + len, sizeof(buf) - len, format, ap); |
332 | if (len > sizeof(buf)) { |
333 | buf[sizeof(buf) - 1] = ']'; |
334 | buf[sizeof(buf) - 2] = '…'; |
335 | buf[sizeof(buf) - 3] = '['; |
336 | len = sizeof(buf); |
337 | } |
338 | |
339 | while (buf[len - 1] == '\r') { |
340 | buf[len - 1] = '\0'; |
341 | len--; |
342 | } |
343 | |
344 | if (logger->buffered_logs_len + len > LOGGER_BUFFERED_LOG_SIZE) |
345 | logger_flush_buffer(false); |
346 | |
347 | len = strlcat(logger->buffered_logs, buf, LOGGER_BUFFERED_LOG_SIZE); |
348 | logger->buffered_logs_len = MIN(len, LOGGER_BUFFERED_LOG_SIZE); |
349 | |
350 | if (logger->autoflush) |
351 | logger_flush_buffer(false); |
352 | |
353 | return len; |
354 | } |
355 | |
356 | void |
357 | logger_flush_buffer(bool force) |
358 | { |
359 | RgnHandle savergn; |
360 | Rect zerorect = { 0, 0, 0, 0 }; |
361 | GrafPtr old_port; |
362 | short line_height = 0, new_lines = 1, n; |
363 | char *buf; |
364 | |
365 | if (!logger || (logger->buffered_logs_len == 0 && !force)) |
366 | return; |
367 | |
368 | for (n = 0; n < logger->buffered_logs_len; n++) { |
369 | if (logger->buffered_logs[n] == '\r') |
370 | new_lines++; |
371 | } |
372 | |
373 | line_height = LOGGER_FONT_SIZE + 3; |
374 | |
375 | GetPort(&old_port); |
376 | SetPort(logger->win); |
377 | |
378 | /* check for TE overflow */ |
379 | |
380 | HLock(logger->messages_te); |
381 | |
382 | /* too many lines */ |
383 | if ((unsigned long)(*(logger->messages_te))->nLines >= |
384 | (nitems((*(logger->messages_te))->lineStarts) - new_lines)) |
385 | goto te_overflow; |
386 | |
387 | /* too many characters */ |
388 | if ((unsigned long)(*(logger->messages_te))->teLength >= |
389 | (SHRT_MAX - logger->buffered_logs_len)) |
390 | goto te_overflow; |
391 | |
392 | /* destRect of all lines is too tall */ |
393 | if ((unsigned long)(*(logger->messages_te))->nLines * line_height >= |
394 | (SHRT_MAX - new_lines)) |
395 | goto te_overflow; |
396 | |
397 | goto no_overflow; |
398 | |
399 | te_overflow: |
400 | savergn = NewRgn(); |
401 | GetClip(savergn); |
402 | /* create an empty clip region so all TE updates are hidden */ |
403 | ClipRect(&zerorect); |
404 | |
405 | /* select some lines at the start, delete them */ |
406 | TESetSelect(0, (*(logger->messages_te))->lineStarts[new_lines * 2], |
407 | logger->messages_te); |
408 | TEDelete(logger->messages_te); |
409 | |
410 | /* scroll up, causing a repaint */ |
411 | TEPinScroll(0, SHRT_MAX, logger->messages_te); |
412 | |
413 | /* then scroll back down to what it looked like before we did anything */ |
414 | TEPinScroll(0, -SHRT_MAX, logger->messages_te); |
415 | |
416 | /* resume normal drawing */ |
417 | SetClip(savergn); |
418 | DisposeRgn(savergn); |
419 | |
420 | no_overflow: |
421 | TESetSelect(SHRT_MAX, SHRT_MAX, logger->messages_te); |
422 | |
423 | buf = logger->buffered_logs; |
424 | if ((*(logger->messages_te))->teLength == 0) { |
425 | /* skip leading \r */ |
426 | logger->buffered_logs_len--; |
427 | buf++; |
428 | } |
429 | |
430 | TEInsert(buf, logger->buffered_logs_len, logger->messages_te); |
431 | |
432 | if ((unsigned long)(*(logger->messages_te))->nLines >= |
433 | nitems((*(logger->messages_te))->lineStarts)) |
434 | warn("te overflow!"); |
435 | |
436 | TEPinScroll(0, -SHRT_MAX, logger->messages_te); |
437 | SetCtlValue(logger->messages_scroller, |
438 | GetCtlMax(logger->messages_scroller)); |
439 | UpdateScrollbarForTE(logger->win, logger->messages_scroller, |
440 | logger->messages_te, false); |
441 | HUnlock(logger->messages_te); |
442 | |
443 | SetPort(old_port); |
444 | |
445 | logger->buffered_logs_len = 0; |
446 | logger->buffered_logs[0] = '\0'; |
447 | } |