source: util.c @ 205e164

release-1.10release-1.7release-1.8release-1.9
Last change on this file since 205e164 was c1f1e1e, checked in by David Benjamin <davidben@mit.edu>, 15 years ago
Add convenience method for connecting a window to a signal
  • Property mode set to 100644
File size: 17.1 KB
Line 
1#include "owl.h"
2#include <stdlib.h>
3#include <string.h>
4#include <unistd.h>
5#include <ctype.h>
6#include <pwd.h>
7#include <sys/stat.h>
8#include <sys/types.h>
9
10#include <glib-object.h>
11
12char **atokenize(const char *buffer, const char *sep, int *i)
13{
14  /* each element of return must be freed by user */
15  char **args;
16  char *workbuff, *foo;
17  int done=0, first=1, count=0;
18
19  workbuff = owl_strdup(buffer);
20
21  args=NULL;
22  while (!done) {
23    if (first) {
24      first=0;
25      foo=strtok(workbuff, sep);
26    } else {
27      foo=strtok(NULL, sep);
28    }
29    if (foo==NULL) {
30      done=1;
31    } else {
32      args=owl_realloc(args, sizeof(char *) * (count+1));
33      args[count] = owl_strdup(foo);
34      count++;
35    }
36  }
37  *i=count;
38  owl_free(workbuff);
39  return(args);
40}
41
42const char *skiptokens(const char *buff, int n) {
43  /* skips n tokens and returns where that would be. */
44  char quote = 0;
45  while (*buff && n>0) {
46      while (*buff == ' ') buff++;
47      while (*buff && (quote || *buff != ' ')) {
48        if(quote) {
49          if(*buff == quote) quote = 0;
50        } else if(*buff == '"' || *buff == '\'') {
51          quote = *buff;
52        }
53        buff++;
54      }
55      while (*buff == ' ') buff++;
56      n--;
57  }
58  return buff;
59}
60
61/* Return a "nice" version of the path.  Tilde expansion is done, and
62 * duplicate slashes are removed.  Caller must free the return.
63 */
64char *owl_util_makepath(const char *in)
65{
66  int i, j, x;
67  char *out, user[MAXPATHLEN];
68  struct passwd *pw;
69
70  out=owl_malloc(MAXPATHLEN+1);
71  out[0]='\0';
72  j=strlen(in);
73  x=0;
74  for (i=0; i<j; i++) {
75    if (in[i]=='~') {
76      if ( (i==(j-1)) ||          /* last character */
77           (in[i+1]=='/') ) {     /* ~/ */
78        /* use my homedir */
79        pw=getpwuid(getuid());
80        if (!pw) {
81          out[x]=in[i];
82        } else {
83          out[x]='\0';
84          strcat(out, pw->pw_dir);
85          x+=strlen(pw->pw_dir);
86        }
87      } else {
88        /* another user homedir */
89        int a, b;
90        b=0;
91        for (a=i+1; i<j; a++) {
92          if (in[a]==' ' || in[a]=='/') {
93            break;
94          } else {
95            user[b]=in[a];
96            i++;
97            b++;
98          }
99        }
100        user[b]='\0';
101        pw=getpwnam(user);
102        if (!pw) {
103          out[x]=in[i];
104        } else {
105          out[x]='\0';
106          strcat(out, pw->pw_dir);
107          x+=strlen(pw->pw_dir);
108        }
109      }
110    } else if (in[i]=='/') {
111      /* check for a double / */
112      if (i<(j-1) && (in[i+1]=='/')) {
113        /* do nothing */
114      } else {
115        out[x]=in[i];
116        x++;
117      }
118    } else {
119      out[x]=in[i];
120      x++;
121    }
122  }
123  out[x]='\0';
124  return(out);
125}
126
127void atokenize_delete(char **tok, int nels)
128{
129  int i;
130  for (i=0; i<nels; i++) {
131    owl_free(tok[i]);
132  }
133  owl_free(tok);
134}
135
136
137void owl_parse_delete(char **argv, int argc)
138{
139  int i;
140
141  if (!argv) return;
142 
143  for (i=0; i<argc; i++) {
144    if (argv[i]) owl_free(argv[i]);
145  }
146  owl_free(argv);
147}
148
149char **owl_parseline(const char *line, int *argc)
150{
151  /* break a command line up into argv, argc.  The caller must free
152     the returned values.  If there is an error argc will be set to
153     -1, argv will be NULL and the caller does not need to free
154     anything */
155
156  char **argv;
157  int i, len, between=1;
158  char *curarg;
159  char quote;
160
161  argv=owl_malloc(sizeof(char *));
162  len=strlen(line);
163  curarg=owl_malloc(len+10);
164  strcpy(curarg, "");
165  quote='\0';
166  *argc=0;
167  for (i=0; i<len+1; i++) {
168    /* find the first real character */
169    if (between) {
170      if (line[i]==' ' || line[i]=='\t' || line[i]=='\0') {
171        continue;
172      } else {
173        between=0;
174        i--;
175        continue;
176      }
177    }
178
179    /* deal with a quote character */
180    if (line[i]=='"' || line[i]=="'"[0]) {
181      /* if this type of quote is open, close it */
182      if (quote==line[i]) {
183        quote='\0';
184        continue;
185      }
186
187      /* if no quoting is open then open with this */
188      if (quote=='\0') {
189        quote=line[i];
190        continue;
191      }
192
193      /* if another type of quote is open then treat this as a literal */
194      curarg[strlen(curarg)+1]='\0';
195      curarg[strlen(curarg)]=line[i];
196      continue;
197    }
198
199    /* if it's not a space or end of command, then use it */
200    if (line[i]!=' ' && line[i]!='\t' && line[i]!='\n' && line[i]!='\0') {
201      curarg[strlen(curarg)+1]='\0';
202      curarg[strlen(curarg)]=line[i];
203      continue;
204    }
205
206    /* otherwise, if we're not in quotes, add the whole argument */
207    if (quote=='\0') {
208      /* add the argument */
209      argv=owl_realloc(argv, sizeof(char *)*((*argc)+1));
210      argv[*argc] = owl_strdup(curarg);
211      *argc=*argc+1;
212      strcpy(curarg, "");
213      between=1;
214      continue;
215    }
216
217    /* if it is a space and we're in quotes, then use it */
218    curarg[strlen(curarg)+1]='\0';
219    curarg[strlen(curarg)]=line[i];
220  }
221
222  owl_free(curarg);
223
224  /* check for unbalanced quotes */
225  if (quote!='\0') {
226    owl_parse_delete(argv, *argc);
227    *argc=-1;
228    return(NULL);
229  }
230
231  return(argv);
232}
233
234/* caller must free the return */
235char *owl_util_minutes_to_timestr(int in)
236{
237  int days, hours;
238  long run;
239  char *out;
240
241  run=in;
242
243  days=run/1440;
244  run-=days*1440;
245  hours=run/60;
246  run-=hours*60;
247
248  if (days>0) {
249    out=owl_sprintf("%i d %2.2i:%2.2li", days, hours, run);
250  } else {
251    out=owl_sprintf("    %2.2i:%2.2li", hours, run);
252  }
253  return(out);
254}
255
256/* hooks for doing memory allocation et. al. in owl */
257
258void *owl_malloc(size_t size)
259{
260  return(g_malloc(size));
261}
262
263void owl_free(void *ptr)
264{
265  g_free(ptr);
266}
267
268char *owl_strdup(const char *s1)
269{
270  return(g_strdup(s1));
271}
272
273void *owl_realloc(void *ptr, size_t size)
274{
275  return(g_realloc(ptr, size));
276}
277
278/* allocates memory and returns the string or null.
279 * caller must free the string.
280 */
281char *owl_sprintf(const char *fmt, ...)
282{
283  va_list ap;
284  char *ret = NULL;
285  va_start(ap, fmt);
286  ret = g_strdup_vprintf(fmt, ap);
287  va_end(ap);
288  return ret;
289}
290
291/* These are in order of their value in owl.h */
292static const struct {
293  int number;
294  const char *name;
295} color_map[] = {
296  {OWL_COLOR_INVALID, "invalid"},
297  {OWL_COLOR_DEFAULT, "default"},
298  {OWL_COLOR_BLACK, "black"},
299  {OWL_COLOR_RED, "red"},
300  {OWL_COLOR_GREEN, "green"},
301  {OWL_COLOR_YELLOW,"yellow"},
302  {OWL_COLOR_BLUE, "blue"},
303  {OWL_COLOR_MAGENTA, "magenta"},
304  {OWL_COLOR_CYAN, "cyan"},
305  {OWL_COLOR_WHITE, "white"},
306};
307
308/* Return the owl color associated with the named color.  Return -1
309 * if the named color is not available
310 */
311int owl_util_string_to_color(const char *color)
312{
313  int c, i;
314  char *p;
315
316  for (i = 0; i < (sizeof(color_map)/sizeof(color_map[0])); i++)
317    if (strcasecmp(color, color_map[i].name) == 0)
318      return color_map[i].number;
319
320  c = strtol(color, &p, 10);
321  if (p != color && c >= -1 && c < COLORS) {
322    return(c);
323  }
324  return(OWL_COLOR_INVALID);
325}
326
327/* Return a string name of the given owl color */
328const char *owl_util_color_to_string(int color)
329{
330  if (color >= OWL_COLOR_INVALID && color <= OWL_COLOR_WHITE)
331    return color_map[color - OWL_COLOR_INVALID].name;
332  return("Unknown color");
333}
334
335/* Get the default tty name.  Caller must free the return */
336char *owl_util_get_default_tty(void)
337{
338  const char *tmp;
339  char *out;
340
341  if (getenv("DISPLAY")) {
342    out=owl_strdup(getenv("DISPLAY"));
343  } else if ((tmp=ttyname(fileno(stdout)))!=NULL) {
344    out=owl_strdup(tmp);
345    if (!strncmp(out, "/dev/", 5)) {
346      owl_free(out);
347      out=owl_strdup(tmp+5);
348    }
349  } else {
350    out=owl_strdup("unknown");
351  }
352  return(out);
353}
354
355/* strip leading and trailing new lines.  Caller must free the
356 * return.
357 */
358char *owl_util_stripnewlines(const char *in)
359{
360 
361  char  *tmp, *ptr1, *ptr2, *out;
362
363  ptr1=tmp=owl_strdup(in);
364  while (ptr1[0]=='\n') {
365    ptr1++;
366  }
367  ptr2=ptr1+strlen(ptr1)-1;
368  while (ptr2>ptr1 && ptr2[0]=='\n') {
369    ptr2[0]='\0';
370    ptr2--;
371  }
372
373  out=owl_strdup(ptr1);
374  owl_free(tmp);
375  return(out);
376}
377
378/* Delete all lines matching "line" from the named file.  If no such
379 * line is found the file is left intact.  If backup==1 then leave a
380 * backup file containing the original contents.  The match is
381 * case-insensitive.
382 *
383 * Returns the number of lines removed
384 */
385int owl_util_file_deleteline(const char *filename, const char *line, int backup)
386{
387  char *backupfile, *newfile, *buf = NULL;
388  FILE *old, *new;
389  struct stat st;
390  int numremoved = 0;
391
392  if ((old = fopen(filename, "r")) == NULL) {
393    owl_function_error("Cannot open %s (for reading): %s",
394                       filename, strerror(errno));
395    return 0;
396  }
397
398  if (fstat(fileno(old), &st) != 0) {
399    owl_function_error("Cannot stat %s: %s", filename, strerror(errno));
400    return 0;
401  }
402
403  newfile = owl_sprintf("%s.new", filename);
404  if ((new = fopen(newfile, "w")) == NULL) {
405    owl_function_error("Cannot open %s (for writing): %s",
406                       filename, strerror(errno));
407    free(newfile);
408    fclose(old);
409    return 0;
410  }
411
412  if (fchmod(fileno(new), st.st_mode & 0777) != 0) {
413    owl_function_error("Cannot set permissions on %s: %s",
414                       filename, strerror(errno));
415    unlink(newfile);
416    fclose(new);
417    free(newfile);
418    fclose(old);
419    return 0;
420  }
421
422  while (owl_getline_chomp(&buf, old))
423    if (strcasecmp(buf, line) != 0)
424      fprintf(new, "%s\n", buf);
425    else
426      numremoved++;
427  owl_free(buf);
428
429  fclose(new);
430  fclose(old);
431
432  if (backup) {
433    backupfile = owl_sprintf("%s.backup", filename);
434    unlink(backupfile);
435    if (link(filename, backupfile) != 0) {
436      owl_function_error("Cannot link %s: %s", backupfile, strerror(errno));
437      owl_free(backupfile);
438      unlink(newfile);
439      owl_free(newfile);
440      return 0;
441    }
442    owl_free(backupfile);
443  }
444
445  if (rename(newfile, filename) != 0) {
446    owl_function_error("Cannot move %s to %s: %s",
447                       newfile, filename, strerror(errno));
448    numremoved = 0;
449  }
450
451  unlink(newfile);
452  owl_free(newfile);
453
454  return numremoved;
455}
456
457int owl_util_max(int a, int b)
458{
459  if (a>b) return(a);
460  return(b);
461}
462
463int owl_util_min(int a, int b)
464{
465  if (a<b) return(a);
466  return(b);
467}
468
469/* Return the base class or instance from a zephyr class, by removing
470   leading `un' or trailing `.d'.
471   The caller is responsible for freeing the allocated string.
472*/
473char * owl_util_baseclass(const char * class)
474{
475  char *start, *end;
476
477  while(!strncmp(class, "un", 2)) {
478    class += 2;
479  }
480
481  start = owl_strdup(class);
482  end = start + strlen(start) - 1;
483  while(end > start && *end == 'd' && *(end-1) == '.') {
484    end -= 2;
485  }
486  *(end + 1) = 0;
487
488  return start;
489}
490
491const char * owl_get_datadir(void)
492{
493  const char * datadir = getenv("BARNOWL_DATA_DIR");
494  if(datadir != NULL)
495    return datadir;
496  return DATADIR;
497}
498
499const char * owl_get_bindir(void)
500{
501  const char * bindir = getenv("BARNOWL_BIN_DIR");
502  if(bindir != NULL)
503    return bindir;
504  return BINDIR;
505}
506
507/* Strips format characters from a valid utf-8 string. Returns the
508   empty string if 'in' does not validate. */
509char * owl_strip_format_chars(const char *in)
510{
511  char *r;
512  if (g_utf8_validate(in, -1, NULL)) {
513    const char *s, *p;
514    r = owl_malloc(strlen(in)+1);
515    r[0] = '\0';
516    s = in;
517    p = strchr(s, OWL_FMTEXT_UC_STARTBYTE_UTF8);
518    while(p) {
519      /* If it's a format character, copy up to it, and skip all
520         immediately following format characters. */
521      if (owl_fmtext_is_format_char(g_utf8_get_char(p))) {
522        strncat(r, s, p-s);
523        p = g_utf8_next_char(p);
524        while (owl_fmtext_is_format_char(g_utf8_get_char(p))) {
525          p = g_utf8_next_char(p);
526        }
527        s = p;
528        p = strchr(s, OWL_FMTEXT_UC_STARTBYTE_UTF8);
529      }
530      else {
531        p = strchr(p+1, OWL_FMTEXT_UC_STARTBYTE_UTF8);
532      }
533    }
534    if (s) strcat(r,s);
535  }
536  else {
537    r = owl_strdup("");
538  }
539  return r;
540}
541
542/* If in is not UTF-8, convert from ISO-8859-1. We may want to allow
543 * the caller to specify an alternative in the future. We also strip
544 * out characters in Unicode Plane 16, as we use that plane internally
545 * for formatting.
546 */
547char * owl_validate_or_convert(const char *in)
548{
549  if (g_utf8_validate(in, -1, NULL)) {
550    return owl_strip_format_chars(in);
551  }
552  else {
553    return g_convert(in, -1,
554                     "UTF-8", "ISO-8859-1",
555                     NULL, NULL, NULL);
556  }
557}
558/*
559 * Validate 'in' as UTF-8, and either return a copy of it, or an empty
560 * string if it is invalid utf-8.
561 */
562char * owl_validate_utf8(const char *in)
563{
564  char *out;
565  if (g_utf8_validate(in, -1, NULL)) {
566    out = owl_strdup(in);
567  } else {
568    out = owl_strdup("");
569  }
570  return out;
571}
572
573/* This is based on _extract() and _isCJ() from perl's Text::WrapI18N */
574int owl_util_can_break_after(gunichar c)
575{
576 
577  if (c == ' ') return 1;
578  if (c >= 0x3000 && c <= 0x312f) {
579    /* CJK punctuations, Hiragana, Katakana, Bopomofo */
580    if (c == 0x300a || c == 0x300c || c == 0x300e ||
581        c == 0x3010 || c == 0x3014 || c == 0x3016 ||
582        c == 0x3018 || c == 0x301a)
583      return 0;
584    return 1;
585  }
586  if (c >= 0x31a0 && c <= 0x31bf) {return 1;}  /* Bopomofo */
587  if (c >= 0x31f0 && c <= 0x31ff) {return 1;}  /* Katakana extension */
588  if (c >= 0x3400 && c <= 0x9fff) {return 1;}  /* Han Ideogram */
589  if (c >= 0xf900 && c <= 0xfaff) {return 1;}  /* Han Ideogram */
590  if (c >= 0x20000 && c <= 0x2ffff) {return 1;}  /* Han Ideogram */
591  return 0;
592}
593
594char *owl_escape_highbit(const char *str)
595{
596  GString *out = g_string_new("");
597  unsigned char c;
598  while((c = (*str++))) {
599    if(c == '\\') {
600      g_string_append(out, "\\\\");
601    } else if(c & 0x80) {
602      g_string_append_printf(out, "\\x%02x", (int)c);
603    } else {
604      g_string_append_c(out, c);
605    }
606  }
607  return g_string_free(out, 0);
608}
609
610/* innards of owl_getline{,_chomp} below */
611static int owl_getline_internal(char **s, FILE *fp, int newline)
612{
613  int size = 0;
614  int target = 0;
615  int count = 0;
616  int c;
617
618  while (1) {
619    c = getc(fp);
620    if ((target + 1) > size) {
621      size += BUFSIZ;
622      *s = owl_realloc(*s, size);
623    }
624    if (c == EOF)
625      break;
626    count++;
627    if (c != '\n' || newline)
628        (*s)[target++] = c;
629    if (c == '\n')
630      break;
631  }
632  (*s)[target] = 0;
633
634  return count;
635}
636
637/* Read a line from fp, allocating memory to hold it, returning the number of
638 * byte read.  *s should either be NULL or a pointer to memory allocated with
639 * owl_malloc; it will be owl_realloc'd as appropriate.  The caller must
640 * eventually free it.  (This is roughly the interface of getline in the gnu
641 * libc).
642 *
643 * The final newline will be included if it's there.
644 */
645int owl_getline(char **s, FILE *fp)
646{
647  return owl_getline_internal(s, fp, 1);
648}
649
650/* As above, but omitting the final newline */
651int owl_getline_chomp(char **s, FILE *fp)
652{
653  return owl_getline_internal(s, fp, 0);
654}
655
656/* Read the rest of the input available in fp into a string. */
657char *owl_slurp(FILE *fp)
658{
659  char *buf = NULL;
660  char *p;
661  int size = 0;
662  int count;
663
664  while (1) {
665    buf = owl_realloc(buf, size + BUFSIZ);
666    p = &buf[size];
667    size += BUFSIZ;
668
669    if ((count = fread(p, 1, BUFSIZ, fp)) < BUFSIZ)
670      break;
671  }
672  p[count] = 0;
673
674  return buf;
675}
676
677gulong owl_dirty_window_on_signal(owl_window *w, gpointer sender, const gchar *detailed_signal)
678{
679  return owl_signal_connect_object(sender, detailed_signal, G_CALLBACK(owl_window_dirty), w, G_CONNECT_SWAPPED);
680}
681
682typedef struct { /*noproto*/
683  GObject  *sender;
684  gulong    signal_id;
685} SignalData;
686
687static void _closure_invalidated(gpointer data, GClosure *closure);
688
689/*
690 * GObject's g_signal_connect_object has a documented bug. This function is
691 * identical except it does not leak the signal handler.
692 */
693gulong owl_signal_connect_object(gpointer sender, const gchar *detailed_signal, GCallback c_handler, gpointer receiver, GConnectFlags connect_flags)
694{
695  g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (sender), 0);
696  g_return_val_if_fail (detailed_signal != NULL, 0);
697  g_return_val_if_fail (c_handler != NULL, 0);
698
699  if (receiver) {
700    SignalData *sdata;
701    GClosure *closure;
702    gulong signal_id;
703
704    g_return_val_if_fail (G_IS_OBJECT (receiver), 0);
705
706    closure = ((connect_flags & G_CONNECT_SWAPPED) ? g_cclosure_new_object_swap : g_cclosure_new_object) (c_handler, receiver);
707    signal_id = g_signal_connect_closure (sender, detailed_signal, closure, connect_flags & G_CONNECT_AFTER);
708
709    /* Register the missing hooks */
710    sdata = g_slice_new0(SignalData);
711    sdata->sender = sender;
712    sdata->signal_id = signal_id;
713
714    g_closure_add_invalidate_notifier(closure, sdata, _closure_invalidated);
715
716    return signal_id;
717  } else {
718    return g_signal_connect_data(sender, detailed_signal, c_handler, NULL, NULL, connect_flags);
719  }
720}
721
722/*
723 * There are three ways the signal could come to an end:
724 *
725 * 1. The user explicitly disconnects it with the returned signal_id.
726 *    - In that case, the disconnection unref's the closure, causing it
727 *      to first be invalidated. The handler's already disconnected, so
728 *      we have no work to do.
729 * 2. The sender gets destroyed.
730 *    - GObject will disconnect each signal which then goes into the above
731 *      case. Our handler does no work.
732 * 3. The receiver gets destroyed.
733 *    - The GClosure was created by g_cclosure_new_object_{,swap} which gets
734 *      invalidated when the receiver is destroyed. We then follow through case 1
735 *      again, but *this* time, the handler has not been disconnected. We then
736 *      clean up ourselves.
737 *
738 * We can't actually hook into this process earlier with weakrefs as GObject
739 * will, on object dispose, first disconnect signals, then invalidate closures,
740 * and notify weakrefs last.
741 */
742static void _closure_invalidated(gpointer data, GClosure *closure)
743{
744  SignalData *sdata = data;
745  if (g_signal_handler_is_connected(sdata->sender, sdata->signal_id)) {
746    g_signal_handler_disconnect(sdata->sender, sdata->signal_id);
747  }
748  g_slice_free(SignalData, sdata);
749}
750
Note: See TracBrowser for help on using the repository browser.