FreeWRL / FreeX3D 4.3.0
ConsoleMessage.c
1/*
2
3
4When running in a plugin, there is no way
5any longer to get the console messages to come up - eg, no
6way to say "Syntax error in X3D file".
7
8Old netscapes used to have a console.
9
10So, now, we pop up xmessages for EACH call here, when running
11as a plugin.
12
13NOTE: Parts of this came from on line examples; apologies
14for loosing the reference. Also, most if it is found in
15"the C programming language" second edition.
16
17*/
18
19
20/****************************************************************************
21 This file is part of the FreeWRL/FreeX3D Distribution.
22
23 Copyright 2009 CRC Canada. (http://www.crc.gc.ca)
24
25 FreeWRL/FreeX3D is free software: you can redistribute it and/or modify
26 it under the terms of the GNU Lesser Public License as published by
27 the Free Software Foundation, either version 3 of the License, or
28 (at your option) any later version.
29
30 FreeWRL/FreeX3D is distributed in the hope that it will be useful,
31 but WITHOUT ANY WARRANTY; without even the implied warranty of
32 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 GNU General Public License for more details.
34
35 You should have received a copy of the GNU General Public License
36 along with FreeWRL/FreeX3D. If not, see <http://www.gnu.org/licenses/>.
37****************************************************************************/
38
39
40
41#include <config.h>
42#include <system.h>
43#include <internal.h>
44
45#include <stdio.h>
46#include <stdarg.h>
47#include <iglobal.h>
48
49
50#define STRING_LENGTH 4096 /* something 'safe' */
51#define MAX_ANDROID_CONSOLE_MESSAGE_SLOTS 100 //5 max number of message lines per frame
52#define MAX_LINE_LENGTH 80 //wrap text here to make it easy for GUI frontends
53#define TAB_SPACES 1
54
55typedef struct pConsoleMessage{
56 int androidFreeSlot;
57 char **androidMessageSlot;
58 int androidHaveUnreadMessages;
59 char FWbuffer [STRING_LENGTH];
60 int maxLineLength;
61 int maxLines;
62 int tabSpaces;
63 void(*callback[2])(char *);
64 void(*callbackB[4])(void*,char*);
65 void *dataB[4];
66 int nbackB;
68static void *ConsoleMessage_constructor(){
69 void *v = MALLOCV(sizeof(struct pConsoleMessage));
70 memset(v,0,sizeof(struct pConsoleMessage));
71 return v;
72}
73void ConsoleMessage_init(struct tConsoleMessage *t){
74 //public
75 //private
76 t->prv = ConsoleMessage_constructor();
77 {
78 int i;
80 p->androidFreeSlot = 0;
81 p->androidHaveUnreadMessages = 0;
82 p->maxLineLength = MAX_LINE_LENGTH;
83 p->maxLines = MAX_ANDROID_CONSOLE_MESSAGE_SLOTS;
84 p->tabSpaces = TAB_SPACES;
85 p->androidMessageSlot = (char**)malloc(MAX_ANDROID_CONSOLE_MESSAGE_SLOTS * sizeof(char*));
86 for (i = 0; i < p->maxLines; i++) p->androidMessageSlot[i] = (char*)NULL;
87 p->callback[0] = NULL;
88 p->callback[1] = NULL;
89 p->nbackB = 0;
90 }
91}
92
93
94//#define MAXMESSAGES 5
95void closeConsoleMessage() {
96 gglobal()->ConsoleMessage.consMsgCount = 0;
97}
98
99
100
101//View / UI part
102void fwg_updateConsoleStatus()
103{
104 //old android method
105 //if you desire to see ConsoleMessages in the View/UI
106 //a) if your View/UI is a console program - call this function once per frame
107 //b) if your View/UI is a GUI program, do something similar in your View/UI code to fetch and display lines in GUI
108 //call once per controller-loop/frame
109 //polls ConsoleMessage.c for accumulated messages and updates console
110 int nlines, i;
111 char *buffer;
112 nlines = fwg_get_unread_message_count(); //poll model
113 for (i = 0; i<nlines; i++)
114 {
115 buffer = fwg_get_last_message(); //poll model for state handover - View now owns buffer
116 printf("%s",buffer); //update UI(view)
117 free(buffer);
118 }
119}
120
121// Model (backend) INTERFACE part
122void fwg_setConsoleParam_maxLines(int maxLines);
123void fwg_setConsoleParam_maxLineLength(int maxLineLength);
124void fwg_setConsoleParam_replaceTabs(int tabSpaces);
125int fwg_get_unread_message_count();
126char *fwg_get_last_message();
127int fwl_StringConsoleMessage(char* consoleBuffer);
128void fwg_updateConsoleStatus(); //for console programs only - sent to printf
129void fwg_register_consolemessage_callback(void(*callback)(char *));
130void fwg_register_consolemessage_callbackB(void* data, void(*callback)(void *data, char *));
131
132
133void fwg_setConsoleParam_maxLines(int maxLines)
134{
136 ttglobal tg = gglobal();
137 if (!tg) return;
138 p = (ppConsoleMessage)tg->ConsoleMessage.prv;
139 if (maxLines > 0){
140 int i;
141 p->androidMessageSlot = realloc(p->androidMessageSlot, maxLines*sizeof(char*)); //array of pointers
142 for (i = p->maxLines; i<maxLines; i++) p->androidMessageSlot[i] = (char *)NULL;
143 p->maxLines = maxLines;
144 }
145
146}
147void fwg_setConsoleParam_maxLineLength(int maxLineLength)
148{
150 ttglobal tg = gglobal();
151 if (!tg) return;
152 p = (ppConsoleMessage)tg->ConsoleMessage.prv;
153 if (maxLineLength > 0)
154 p->maxLineLength = maxLineLength;
155}
156void fwg_setConsoleParam_replaceTabs(int tabSpaces)
157{
159 ttglobal tg = gglobal();
160 if (!tg) return;
161 p = (ppConsoleMessage)tg->ConsoleMessage.prv;
162 if (tabSpaces > 0)
163 p->tabSpaces = tabSpaces;
164
165}
166void fwg_register_consolemessage_callback(void(*callback)(char *))
167{
168 //new method
169 //if your frontend is in C, you can register something like printf here as a callback
170 //advantage over polling once per loop: when debugging you may want to see console output
171 //more often during a single loop - this should come out as soon as written in the program
172 //if message ends in \n
173 //you can call 0,1 or 2 times during program run ie to set a printf and a logfile
174 // \t and \n will still be in the string (it won't be pre-split)
175 int iback;
177 ttglobal tg = gglobal();
178 if (!tg) return;
179 p = (ppConsoleMessage)tg->ConsoleMessage.prv;
180 iback = 0;
181 if (p->callback[iback]) iback++;
182 p->callback[iback] = callback;
183}
184void fwg_register_consolemessage_callbackB(void* data, void(*callback)(void*,char *))
185{
186 //new method
187 //this version of callback registration takes an arbitrary data pointer
188 //if your frontend is in C, you can register something like printf here as a callback
189 //advantage over polling once per loop: when debugging you may want to see console output
190 //more often during a single loop - this should come out as soon as written in the program
191 //if message ends in \n
192 //you can call 0,1 or 2 times during program run ie to set a printf and a logfile
193 // \t and \n will still be in the string (it won't be pre-split)
195 ttglobal tg = gglobal();
196 if (!tg) return;
197 p = (ppConsoleMessage)tg->ConsoleMessage.prv;
198 p->callbackB[p->nbackB] = callback;
199 p->dataB[p->nbackB] = data;
200 p->nbackB++;
201
202}
203// tell the UI how many unread console messages we have.
204int fwg_get_unread_message_count() {
205 //old android method
207 ttglobal tg = gglobal();
208 if (!tg) return 0;
209 p = (ppConsoleMessage)tg->ConsoleMessage.prv;
210 return p->androidHaveUnreadMessages;
211}
212
213char *fwg_get_last_message() {
214 /*
215 old android method
216 Transfers ownership of a ConsoleMessage line to the View/UI caller
217 - returns NULL if no more messages waiting on this frame (check again next frame)
218 - there is no \n in string, it has already been split into screen lines
219 - by default, \t is replaced with one space
220 - long lines will already be split to maxLineLength
221 - on each frame, loop over fwg_get_unread_message_count()
222 or until fwg_get_last_message() returns null to get all the messages
223 */
225 ttglobal tg = gglobal();
226 int whm, nmess;
227
228 if (!tg) return "NO GGLOBAL - NO MESSAGES";
229 p = (ppConsoleMessage)tg->ConsoleMessage.prv;
230
231 // reset the "number of messages" counter.
232 nmess = p->androidHaveUnreadMessages;
233 p->androidHaveUnreadMessages--;
234
235 // which message from our rotating pool do we want?
236 whm = p->androidFreeSlot - nmess; // +whichOne;
237 if (whm < 0) whm += p->maxLines;
238 if (whm < 0)
239 return NULL; // strdup(""); //none left - View is asking for too many on this frame.
240 if (p->androidMessageSlot[whm] == NULL)
241 return NULL; // strdup(""); //blank string - likely a programming error
242
243 return strdup(p->androidMessageSlot[whm]);
244}
245
246int fwl_StringConsoleMessage(char* consoleBuffer) {
247 return ConsoleMessage("%s",consoleBuffer);
248}
249
250
251// Model (backend) internal part
252static void android_save_log(char *thislog) {
253 /*
254 old android method
255 processes thislog, and accumulates an array simple lines:
256 - splits thislog on each \n
257 - if no \n, holds the pointer on the current line
258 - replaces \t with a blank by default
259 - replaces \n with \0
260 - overwrites array at maxLines (wrap-around use of limited array on each frame)
261 - you can retrieve the array of lines with another function, usually once per frame
262 - or if you don't retrieve, it will continue to wrap around harmlessly
263 */
264 int i, more;
265 char *ln, *buf;
266 ttglobal tg = gglobal();
267 ppConsoleMessage p = (ppConsoleMessage)tg->ConsoleMessage.prv;
268 // sanity check the string, otherwise dalvik can croak if invalid chars
269 for (i = 0; i<(int)strlen(thislog); i++) {
270 thislog[i] = thislog[i] & 0x7f;
271 }
272
273 buf = thislog;
274 more = (buf && *buf > '\0');
275 while (more)
276 {
277 BOOL eol = FALSE;
278 int len; //, ipos = 0;
279 ln = strchr(buf, '\n');
280 len = strlen(buf);
281 if (ln)
282 {
283 *ln = '\0';
284 eol = TRUE;
285 len = strlen(buf);
286 *ln = '\n';
287 }
288
289
290 /* free our copy of this string if required; then set the pointer for this slot
291 to our free slot */
292 //problem: strdup and strcat fragment memory if used a lot
293 if (len || eol)
294 {
295 int llen;
296 char *lstr;
297 if (p->androidMessageSlot[p->androidFreeSlot]){
298 //do no-end-of-line-on-last-one continuation line concatonation
299 char *catsize, *oldsize;
300 int len1, len2;
301 len1 = strlen(p->androidMessageSlot[p->androidFreeSlot]);
302 len2 = len+1;
303 //will need a string buffer to hold combined last line and current continuation line
304 //(there is a re_strcat() function in JScript.c about line 808 that might work here -no time to try it)
305 catsize = (char*)malloc(len1 + len2 + 1);
306 memcpy(catsize, p->androidMessageSlot[p->androidFreeSlot], len1 + 1);
307 oldsize = p->androidMessageSlot[p->androidFreeSlot];
308 p->androidMessageSlot[p->androidFreeSlot] = catsize;
309 free(oldsize);
310 p->androidMessageSlot[p->androidFreeSlot] = strcat(p->androidMessageSlot[p->androidFreeSlot], buf);
311 }
312 else
313 p->androidMessageSlot[p->androidFreeSlot] = strdup(buf);
314
315 //tab expansion (into spaces) might go in here before checking line length
316 if (p->tabSpaces)
317 {
318 char *tt = strchr(p->androidMessageSlot[p->androidFreeSlot], '\t');
319 while (tt) {
320 *tt = ' '; //currently it only replaces 1:1 tab with space - feel free to really tab
321 tt = strchr(p->androidMessageSlot[p->androidFreeSlot], '\t');
322 }
323 }
324
325 //check for line length and wrap-around if necessary
326 lstr = p->androidMessageSlot[p->androidFreeSlot];
327 llen = strlen(lstr);
328 if ( llen > p->maxLineLength){
329 char *remainder = &lstr[p->maxLineLength - 2];
330 buf = strdup(remainder); //how remember to delete this?
331 if(thislog)
332 free(thislog);
333 thislog = buf;
334 p->androidMessageSlot[p->androidFreeSlot][p->maxLineLength - 2] = '\n';
335 p->androidMessageSlot[p->androidFreeSlot][p->maxLineLength - 1] = '\0';
336 eol = TRUE;
337 }else{
338 if (eol){
339 buf = &ln[1];
340 }
341 else{
342 buf = NULL;
343 }
344 }
345 // indicate we have messages
346 if (eol) {
347 char *ln = strchr(p->androidMessageSlot[p->androidFreeSlot], '\n');
348 if (ln)
349 *ln = '\0'; //clear \n
350 /* go to next slot, wrap around*/
351 p->androidFreeSlot++;
352 if (p->androidFreeSlot >= p->maxLines) p->androidFreeSlot = 0;
353 if (p->androidMessageSlot[p->androidFreeSlot] != NULL) {
354 //FREE_IF_NZ(p->androidMessageSlot[p->androidFreeSlot]);
355 if(p->androidMessageSlot[p->androidFreeSlot]){
356 free(p->androidMessageSlot[p->androidFreeSlot]);
357 p->androidMessageSlot[p->androidFreeSlot] = NULL;
358 }
359 }
360 p->androidHaveUnreadMessages++;
361 }
362 }
363 more = (buf && *buf > '\0');
364 }
365 free(thislog);
366 p->androidHaveUnreadMessages = min(p->androidHaveUnreadMessages, p->maxLines -1);
367}
368int fwvsnprintf(char *buffer, int buffer_length, const char *fmt, va_list ap)
369{
370 int i, j, count;
371 //char tempbuf[STRING_LENGTH];
372 //char format[STRING_LENGTH];
373 char *tempbuf;
374 char *format;
375 char c;
376 double d;
377 unsigned u;
378 char *s;
379 void *v;
380 tempbuf = malloc(buffer_length);
381 format = malloc(buffer_length);
382 count = 0;
383 buffer[0] = '\0';
384 while (*fmt)
385 {
386 tempbuf[0] = '\0';
387 for (j = 0; fmt[j] && fmt[j] != '%'; j++) {
388 format[j] = fmt[j]; /* not a format string */
389 }
390
391 if (j) {
392 format[j] = '\0';
393 count += sprintf(tempbuf, "%s", format);/* printf it verbatim */
394 fmt += j;
395 }
396 else {
397 for (j = 0; !isalpha(fmt[j]); j++) { /* find end of format specifier */
398 format[j] = fmt[j];
399 if (j && fmt[j] == '%') /* special case printing '%' */
400 break;
401 }
402 format[j] = fmt[j]; /* finish writing specifier */
403 format[j + 1] = '\0'; /* don't forget NULL terminator */
404 fmt += j + 1;
405
406 switch (format[j]) { /* cases for all specifiers */
407 case 'd':
408 case 'i': /* many use identical actions */
409 i = va_arg(ap, int); /* process the argument */
410 count += sprintf(tempbuf, format, i); /* and printf it */
411 break;
412 case 'o':
413 case 'x':
414 case 'X':
415 case 'u':
416 u = va_arg(ap, unsigned);
417 count += sprintf(tempbuf, format, u);
418 break;
419 case 'c':
420 c = (char)va_arg(ap, int); /* must cast! */
421 count += sprintf(tempbuf, format, c);
422 break;
423 case 's':
424 s = va_arg(ap, char *);
425 if (s){
426 /* limit string to a certain length */
427 if ((int)(strlen(s) + count) > buffer_length) {
428 char tmpstr[100];
429 int ltc;
430 ltc = (int)strlen(s);
431 if (ltc > 80) ltc = 80;
432 memcpy(tmpstr, s, ltc);
433 tmpstr[ltc] = '.'; ltc++;
434 tmpstr[ltc] = '.'; ltc++;
435 tmpstr[ltc] = '.'; ltc++;
436 tmpstr[ltc] = '\0';
437
438 count += sprintf(tempbuf, format, tmpstr);
439 }
440 else count += sprintf(tempbuf, format, s);
441 }
442 break;
443 case 'f':
444 case 'e':
445 case 'E':
446 case 'g':
447 case 'G':
448 d = va_arg(ap, double);
449 count += sprintf(tempbuf, format, d);
450 break;
451 case 'p':
452 v = va_arg(ap, void *);
453 count += sprintf(tempbuf, format, v);
454 break;
455 case 'n':
456 count += sprintf(tempbuf, "%d", count);
457 break;
458 case '%':
459 count += sprintf(tempbuf, "%%");
460 break;
461 default:
462 ERROR_MSG("ConsoleMessage: invalid format specifier: %c\n", format[j]);
463 }
464 }
465 if ((int)(strlen(tempbuf) + strlen(buffer)) < (buffer_length)-10)
466 {
467 strcat(buffer, tempbuf);
468 }
469 }
470 free(tempbuf);
471 free(format);
472 return 1;
473}
474int ConsoleMessage0(const char *fmt, va_list args){
475 int retval;
477 ttglobal tg = gglobal();
478 if (!tg) return 0;
479 p = (ppConsoleMessage)tg->ConsoleMessage.prv;
480 if(p){
481 retval = fwvsnprintf(p->FWbuffer, STRING_LENGTH - 1, fmt, args); /*hope STRING_LENGTH is long enough, else -1 skip */
482 if (retval >= 0){
483 if (p->callback[0])
484 p->callback[0](p->FWbuffer);
485 if (p->callback[1])
486 p->callback[1](p->FWbuffer);
487 if(p->nbackB){
488 //this type used by contenttype_textpanel in mainloop
489 int i;
490 for(i=0;i<p->nbackB;i++)
491 p->callbackB[i](p->dataB[i],p->FWbuffer);
492 }
493// #ifdef _ANDROID
494// DROIDDEBUG(STRDUP(p->FWbuffer)); //passing ownerhsip in
495// #else
497// #endif
498 }
499 }
500 return retval;
501}
502
503
504int ConsoleMessage(const char *fmt, ...)
505{
506 /*
507 There's lots I don't understand such as aqua vs motif vs ??? and plugin vs ??? and the sound/speaker method
508 Q. if we call ConsoleMessage from any thread, should there be a thread lock on something,
509 for example s_list_t *conlist (see hudConsoleMessage() in statusbarHud.c)?
510 */
511
512 va_list args;
513 va_start( args, fmt );
514 return ConsoleMessage0(fmt,args);
515}