FreeWRL/FreeX3D  3.0.0
ConsoleMessage.c
1 /*
2 
3 
4 When running in a plugin, there is no way
5 any longer to get the console messages to come up - eg, no
6 way to say "Syntax error in X3D file".
7 
8 Old netscapes used to have a console.
9 
10 So, now, we pop up xmessages for EACH call here, when running
11 as a plugin.
12 
13 NOTE: Parts of this came from on line examples; apologies
14 for 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 
55 typedef 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;
68 static void *ConsoleMessage_constructor(){
69  void *v = MALLOCV(sizeof(struct pConsoleMessage));
70  memset(v,0,sizeof(struct pConsoleMessage));
71  return v;
72 }
73 void 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
95 void closeConsoleMessage() {
96  gglobal()->ConsoleMessage.consMsgCount = 0;
97 }
98 
99 
100 
101 //View / UI part
102 void 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
122 void fwg_setConsoleParam_maxLines(int maxLines);
123 void fwg_setConsoleParam_maxLineLength(int maxLineLength);
124 void fwg_setConsoleParam_replaceTabs(int tabSpaces);
125 int fwg_get_unread_message_count();
126 char *fwg_get_last_message();
127 int fwl_StringConsoleMessage(char* consoleBuffer);
128 void fwg_updateConsoleStatus(); //for console programs only - sent to printf
129 void fwg_register_consolemessage_callback(void(*callback)(char *));
130 void fwg_register_consolemessage_callbackB(void* data, void(*callback)(void *data, char *));
131 
132 
133 void 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 }
147 void 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 }
156 void 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 }
166 void 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 }
184 void 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.
204 int 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 
213 char *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 
246 int fwl_StringConsoleMessage(char* consoleBuffer) {
247  return ConsoleMessage("%s",consoleBuffer);
248 }
249 
250 
251 // Model (backend) internal part
252 static 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 }
368 int 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  strncpy(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 }
474 int 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 
504 int 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 }