source: util.c @ efef058

release-1.8
Last change on this file since efef058 was d98e501, checked in by David Benjamin <davidben@mit.edu>, 13 years ago
Workaround color-pairs and broken build on Solaris locker build This reverts commit a71b4e049b7d1a44ddd3f918e67c45e2f8bbef2f. Really should have verified the Solaris build first. Everything is ready for us to not need this change, but the locker build of ncurses needs to be updated. In the meantime, hack around the bug by clamping color pairs. Also revert wbkgrndset because it requires cchar_t. That gets defined on _XOPEN_SOURCE_EXTENDED or _XOPEN_SOURCE>=500. Unfortunately, the check for the latter was added after the locker version, and Solaris is a stickler about flags so _XOPEN_SOURCE>=500 and _XOPEN_SOURCE_EXTENDED conflict. Revert this change when we bundle a newer ncurses.
  • Property mode set to 100644
File size: 21.7 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#include <assert.h>
10#include <stdarg.h>
11#include <glib.h>
12#include <glib/gstdio.h>
13#include <glib-object.h>
14
15const char *skiptokens(const char *buff, int n) {
16  /* skips n tokens and returns where that would be. */
17  char quote = 0;
18  while (*buff && n>0) {
19      while (*buff == ' ') buff++;
20      while (*buff && (quote || *buff != ' ')) {
21        if(quote) {
22          if(*buff == quote) quote = 0;
23        } else if(*buff == '"' || *buff == '\'') {
24          quote = *buff;
25        }
26        buff++;
27      }
28      while (*buff == ' ') buff++;
29      n--;
30  }
31  return buff;
32}
33
34CALLER_OWN char *owl_util_homedir_for_user(const char *name)
35{
36  int err;
37  struct passwd pw_buf;
38  struct passwd *pw;
39
40  char *pw_strbuf, *ret;
41  long pw_strbuf_len = sysconf(_SC_GETPW_R_SIZE_MAX);
42  if (pw_strbuf_len < 0) {
43    /* If we really hate ourselves, we can be fancy and loop until we stop
44     * getting ERANGE. For now just pick a random number. */
45    owl_function_error("owl_util_homedir_for_user: Could not get _SC_GETPW_R_SIZE_MAX");
46    pw_strbuf_len = 16384;
47  }
48  pw_strbuf = g_new0(char, pw_strbuf_len);
49  err = getpwnam_r(name, &pw_buf, pw_strbuf, pw_strbuf_len, &pw);
50  if (err) {
51    owl_function_error("getpwuid_r: %s", strerror(err));
52    /* Fall through; pw will be NULL. */
53  }
54  ret = pw ? g_strdup(pw->pw_dir) : NULL;
55  g_free(pw_strbuf);
56  return ret;
57}
58
59/* Return a "nice" version of the path.  Tilde expansion is done, and
60 * duplicate slashes are removed.  Caller must free the return.
61 */
62CALLER_OWN char *owl_util_makepath(const char *in)
63{
64  char *out;
65  int i, j;
66  if (in[0] == '~') {
67    /* Attempt tilde-expansion of the first component. Get the
68       tilde-prefix, which goes up to the next slash. */
69    const char *end = strchr(in + 1, '/');
70    if (end == NULL)
71      end = in + strlen(in);
72
73    /* Patch together a new path. Replace the ~ and tilde-prefix with
74       the homedir, if available. */
75    if (end == in + 1) {
76      /* My home directory. Use the one in owl_global for consistency with
77       * owl_zephyr_dotfile. */
78      out = g_strconcat(owl_global_get_homedir(&g), end, NULL);
79    } else {
80      /* Someone else's home directory. */
81      char *user = g_strndup(in + 1, end - (in + 1));
82      char *home = owl_util_homedir_for_user(user);
83      if (home) {
84        out = g_strconcat(home, end, NULL);
85      } else {
86        out = g_strdup(in);
87      }
88      g_free(home);
89      g_free(user);
90    }
91  } else {
92    out = g_strdup(in);
93  }
94
95  /* And a quick pass to remove duplicate slashes. */
96  for (i = j = 0; out[i] != '\0'; i++) {
97    if (out[i] != '/' || i == 0 || out[i-1] != '/')
98      out[j++] = out[i];
99  }
100  out[j] = '\0';
101  return out;
102}
103
104void owl_ptr_array_free(GPtrArray *array, GDestroyNotify element_free_func)
105{
106  /* TODO: when we move to requiring glib 2.22+, use
107   * g_ptr_array_new_with_free_func instead. */
108  if (element_free_func)
109    g_ptr_array_foreach(array, (GFunc)element_free_func, NULL);
110  g_ptr_array_free(array, true);
111}
112
113/* Break a command line up into argv, argc.  The caller must free
114   the returned values with g_strfreev.  If there is an error argc will be set
115   to -1, argv will be NULL and the caller does not need to free anything. The
116   returned vector is NULL-terminated. */
117CALLER_OWN char **owl_parseline(const char *line, int *argc)
118{
119  GPtrArray *argv;
120  int i, len, between=1;
121  GString *curarg;
122  char quote;
123
124  argv = g_ptr_array_new();
125  len=strlen(line);
126  curarg = g_string_new("");
127  quote='\0';
128  if (argc) *argc=0;
129  for (i=0; i<len+1; i++) {
130    /* find the first real character */
131    if (between) {
132      if (line[i]==' ' || line[i]=='\t' || line[i]=='\0') {
133        continue;
134      } else {
135        between=0;
136        i--;
137        continue;
138      }
139    }
140
141    /* deal with a quote character */
142    if (line[i]=='"' || line[i]=="'"[0]) {
143      /* if this type of quote is open, close it */
144      if (quote==line[i]) {
145        quote='\0';
146        continue;
147      }
148
149      /* if no quoting is open then open with this */
150      if (quote=='\0') {
151        quote=line[i];
152        continue;
153      }
154
155      /* if another type of quote is open then treat this as a literal */
156      g_string_append_c(curarg, line[i]);
157      continue;
158    }
159
160    /* if it's not a space or end of command, then use it */
161    if (line[i]!=' ' && line[i]!='\t' && line[i]!='\n' && line[i]!='\0') {
162      g_string_append_c(curarg, line[i]);
163      continue;
164    }
165
166    /* otherwise, if we're not in quotes, add the whole argument */
167    if (quote=='\0') {
168      /* add the argument */
169      g_ptr_array_add(argv, g_string_free(curarg, false));
170      curarg = g_string_new("");
171      between=1;
172      continue;
173    }
174
175    /* if it is a space and we're in quotes, then use it */
176    g_string_append_c(curarg, line[i]);
177  }
178
179  if (argc) *argc = argv->len;
180  g_ptr_array_add(argv, NULL);
181  g_string_free(curarg, true);
182
183  /* check for unbalanced quotes */
184  if (quote!='\0') {
185    owl_ptr_array_free(argv, g_free);
186    if (argc) *argc = -1;
187    return(NULL);
188  }
189
190  return (char**)g_ptr_array_free(argv, false);
191}
192
193/* Appends a quoted version of arg suitable for placing in a
194 * command-line to a GString. Does not append a space. */
195void owl_string_append_quoted_arg(GString *buf, const char *arg)
196{
197  const char *argp;
198  if (arg[0] == '\0') {
199    /* Quote the empty string. */
200    g_string_append(buf, "''");
201  } else if (arg[strcspn(arg, "'\" \n\t")] == '\0') {
202    /* If there are no nasty characters, return as-is. */
203    g_string_append(buf, arg);
204  } else if (!strchr(arg, '\'')) {
205    /* Single-quote if possible. */
206    g_string_append_c(buf, '\'');
207    g_string_append(buf, arg);
208    g_string_append_c(buf, '\'');
209  } else {
210    /* Nasty case: double-quote, but change all internal "s to "'"'"
211     * so that they are single-quoted because we're too cool for
212     * backslashes.
213     */
214    g_string_append_c(buf, '"');
215    for (argp = arg; *argp; argp++) {
216      if (*argp == '"')
217        g_string_append(buf, "\"'\"'\"");
218      else
219        g_string_append_c(buf, *argp);
220    }
221    g_string_append_c(buf, '"');
222  }
223}
224
225/*
226 * Appends 'tmpl' to 'buf', replacing any instances of '%q' with arguments from
227 * the varargs provided, quoting them to be safe for placing in a BarnOwl
228 * command line.
229 */
230void owl_string_appendf_quoted(GString *buf, const char *tmpl, ...)
231{
232  va_list ap;
233  va_start(ap, tmpl);
234  owl_string_vappendf_quoted(buf, tmpl, ap);
235  va_end(ap);
236}
237
238void owl_string_vappendf_quoted(GString *buf, const char *tmpl, va_list ap)
239{
240  const char *p = tmpl, *last = tmpl;
241  while (true) {
242    p = strchr(p, '%');
243    if (p == NULL) break;
244    if (*(p+1) != 'q') {
245      p++;
246      if (*p) p++;
247      continue;
248    }
249    g_string_append_len(buf, last, p - last);
250    owl_string_append_quoted_arg(buf, va_arg(ap, char *));
251    p += 2; last = p;
252  }
253
254  g_string_append(buf, last);
255}
256
257CALLER_OWN char *owl_string_build_quoted(const char *tmpl, ...)
258{
259  GString *buf = g_string_new("");
260  va_list ap;
261  va_start(ap, tmpl);
262  owl_string_vappendf_quoted(buf, tmpl, ap);
263  va_end(ap);
264  return g_string_free(buf, false); 
265}
266
267/* Returns a quoted version of arg suitable for placing in a
268 * command-line. Result should be freed with g_free. */
269CALLER_OWN char *owl_arg_quote(const char *arg)
270{
271  GString *buf = g_string_new("");;
272  owl_string_append_quoted_arg(buf, arg);
273  return g_string_free(buf, false);
274}
275
276/* caller must free the return */
277CALLER_OWN char *owl_util_minutes_to_timestr(int in)
278{
279  int days, hours;
280  long run;
281  char *out;
282
283  run=in;
284
285  days=run/1440;
286  run-=days*1440;
287  hours=run/60;
288  run-=hours*60;
289
290  if (days>0) {
291    out=g_strdup_printf("%i d %2.2i:%2.2li", days, hours, run);
292  } else {
293    out=g_strdup_printf("    %2.2i:%2.2li", hours, run);
294  }
295  return(out);
296}
297
298/* These are in order of their value in owl.h */
299static const struct {
300  int number;
301  const char *name;
302} color_map[] = {
303  {OWL_COLOR_INVALID, "invalid"},
304  {OWL_COLOR_DEFAULT, "default"},
305  {OWL_COLOR_BLACK, "black"},
306  {OWL_COLOR_RED, "red"},
307  {OWL_COLOR_GREEN, "green"},
308  {OWL_COLOR_YELLOW,"yellow"},
309  {OWL_COLOR_BLUE, "blue"},
310  {OWL_COLOR_MAGENTA, "magenta"},
311  {OWL_COLOR_CYAN, "cyan"},
312  {OWL_COLOR_WHITE, "white"},
313};
314
315/* Return the owl color associated with the named color.  Return -1
316 * if the named color is not available
317 */
318int owl_util_string_to_color(const char *color)
319{
320  int c, i;
321  char *p;
322
323  for (i = 0; i < (sizeof(color_map)/sizeof(color_map[0])); i++)
324    if (strcasecmp(color, color_map[i].name) == 0)
325      return color_map[i].number;
326
327  c = strtol(color, &p, 10);
328  if (p != color && c >= -1 && c < COLORS) {
329    return(c);
330  }
331  return(OWL_COLOR_INVALID);
332}
333
334/* Return a string name of the given owl color */
335const char *owl_util_color_to_string(int color)
336{
337  if (color >= OWL_COLOR_INVALID && color <= OWL_COLOR_WHITE)
338    return color_map[color - OWL_COLOR_INVALID].name;
339  return("Unknown color");
340}
341
342/* Get the default tty name.  Caller must free the return */
343CALLER_OWN char *owl_util_get_default_tty(void)
344{
345  const char *tmp;
346  char *out;
347
348  if (getenv("DISPLAY")) {
349    out=g_strdup(getenv("DISPLAY"));
350  } else if ((tmp=ttyname(fileno(stdout)))!=NULL) {
351    out=g_strdup(tmp);
352    if (!strncmp(out, "/dev/", 5)) {
353      g_free(out);
354      out=g_strdup(tmp+5);
355    }
356  } else {
357    out=g_strdup("unknown");
358  }
359  return(out);
360}
361
362/* strip leading and trailing new lines.  Caller must free the
363 * return.
364 */
365CALLER_OWN char *owl_util_stripnewlines(const char *in)
366{
367 
368  char  *tmp, *ptr1, *ptr2, *out;
369
370  ptr1=tmp=g_strdup(in);
371  while (ptr1[0]=='\n') {
372    ptr1++;
373  }
374  ptr2=ptr1+strlen(ptr1)-1;
375  while (ptr2>ptr1 && ptr2[0]=='\n') {
376    ptr2[0]='\0';
377    ptr2--;
378  }
379
380  out=g_strdup(ptr1);
381  g_free(tmp);
382  return(out);
383}
384
385
386/* If filename is a link, recursively resolve symlinks.  Otherwise, return the filename
387 * unchanged.  On error, call owl_function_error and return NULL.
388 *
389 * This function assumes that filename eventually resolves to an acutal file.
390 * If you want to check this, you should stat() the file first.
391 *
392 * The caller of this function is responsible for freeing the return value.
393 *
394 * Error conditions are the same as g_file_read_link.
395 */
396CALLER_OWN gchar *owl_util_recursive_resolve_link(const char *filename)
397{
398  gchar *last_path = g_strdup(filename);
399  GError *err = NULL;
400
401  while (g_file_test(last_path, G_FILE_TEST_IS_SYMLINK)) {
402    gchar *link_path = g_file_read_link(last_path, &err);
403    if (link_path == NULL) {
404      owl_function_error("Cannot resolve symlink %s: %s",
405                         last_path, err->message);
406      g_error_free(err);
407      g_free(last_path);
408      return NULL;
409    }
410
411    /* Deal with obnoxious relative paths. If we really care, all this
412     * is racy. Whatever. */
413    if (!g_path_is_absolute(link_path)) {
414      char *last_dir = g_path_get_dirname(last_path);
415      char *tmp = g_build_filename(last_dir, link_path, NULL);
416      g_free(last_dir);
417      g_free(link_path);
418      link_path = tmp;
419    }
420
421    g_free(last_path);
422    last_path = link_path;
423  }
424  return last_path;
425}
426
427/* Delete all lines matching "line" from the named file.  If no such
428 * line is found the file is left intact.  If backup==1 then leave a
429 * backup file containing the original contents.  The match is
430 * case-insensitive.
431 *
432 * Returns the number of lines removed on success.  Returns -1 on failure.
433 */
434int owl_util_file_deleteline(const char *filename, const char *line, int backup)
435{
436  char *backupfile, *newfile, *buf = NULL;
437  gchar *actual_filename; /* gchar; we need to g_free it */
438  FILE *old, *new;
439  struct stat st;
440  int numremoved = 0;
441
442  if ((old = fopen(filename, "r")) == NULL) {
443    owl_function_error("Cannot open %s (for reading): %s",
444                       filename, strerror(errno));
445    return -1;
446  }
447
448  if (fstat(fileno(old), &st) != 0) {
449    owl_function_error("Cannot stat %s: %s", filename, strerror(errno));
450    return -1;
451  }
452
453  /* resolve symlinks, because link() fails on symlinks, at least on AFS */
454  actual_filename = owl_util_recursive_resolve_link(filename);
455  if (actual_filename == NULL)
456    return -1; /* resolving the symlink failed, but we already logged this error */
457
458  newfile = g_strdup_printf("%s.new", actual_filename);
459  if ((new = fopen(newfile, "w")) == NULL) {
460    owl_function_error("Cannot open %s (for writing): %s",
461                       actual_filename, strerror(errno));
462    g_free(newfile);
463    fclose(old);
464    g_free(actual_filename);
465    return -1;
466  }
467
468  if (fchmod(fileno(new), st.st_mode & 0777) != 0) {
469    owl_function_error("Cannot set permissions on %s: %s",
470                       actual_filename, strerror(errno));
471    unlink(newfile);
472    fclose(new);
473    g_free(newfile);
474    fclose(old);
475    g_free(actual_filename);
476    return -1;
477  }
478
479  while (owl_getline_chomp(&buf, old))
480    if (strcasecmp(buf, line) != 0)
481      fprintf(new, "%s\n", buf);
482    else
483      numremoved++;
484  g_free(buf);
485
486  fclose(new);
487  fclose(old);
488
489  if (backup) {
490    backupfile = g_strdup_printf("%s.backup", actual_filename);
491    unlink(backupfile);
492    if (link(actual_filename, backupfile) != 0) {
493      owl_function_error("Cannot link %s: %s", backupfile, strerror(errno));
494      g_free(backupfile);
495      unlink(newfile);
496      g_free(newfile);
497      return -1;
498    }
499    g_free(backupfile);
500  }
501
502  if (rename(newfile, actual_filename) != 0) {
503    owl_function_error("Cannot move %s to %s: %s",
504                       newfile, actual_filename, strerror(errno));
505    numremoved = -1;
506  }
507
508  unlink(newfile);
509  g_free(newfile);
510
511  g_free(actual_filename);
512
513  return numremoved;
514}
515
516/* Return the base class or instance from a zephyr class, by removing
517   leading `un' or trailing `.d'.
518   The caller is responsible for freeing the allocated string.
519*/
520CALLER_OWN char *owl_util_baseclass(const char *class)
521{
522  char *start, *end;
523
524  while(!strncmp(class, "un", 2)) {
525    class += 2;
526  }
527
528  start = g_strdup(class);
529  end = start + strlen(start) - 1;
530  while(end > start && *end == 'd' && *(end-1) == '.') {
531    end -= 2;
532  }
533  *(end + 1) = 0;
534
535  return start;
536}
537
538const char * owl_get_datadir(void)
539{
540  const char * datadir = getenv("BARNOWL_DATA_DIR");
541  if(datadir != NULL)
542    return datadir;
543  return DATADIR;
544}
545
546const char * owl_get_bindir(void)
547{
548  const char * bindir = getenv("BARNOWL_BIN_DIR");
549  if(bindir != NULL)
550    return bindir;
551  return BINDIR;
552}
553
554/* Strips format characters from a valid utf-8 string. Returns the
555   empty string if 'in' does not validate.  Caller must free the return. */
556CALLER_OWN char *owl_strip_format_chars(const char *in)
557{
558  char *r;
559  if (g_utf8_validate(in, -1, NULL)) {
560    const char *s, *p;
561    r = g_new(char, strlen(in)+1);
562    r[0] = '\0';
563    s = in;
564    p = strchr(s, OWL_FMTEXT_UC_STARTBYTE_UTF8);
565    while(p) {
566      /* If it's a format character, copy up to it, and skip all
567         immediately following format characters. */
568      if (owl_fmtext_is_format_char(g_utf8_get_char(p))) {
569        strncat(r, s, p-s);
570        p = g_utf8_next_char(p);
571        while (owl_fmtext_is_format_char(g_utf8_get_char(p))) {
572          p = g_utf8_next_char(p);
573        }
574        s = p;
575        p = strchr(s, OWL_FMTEXT_UC_STARTBYTE_UTF8);
576      }
577      else {
578        p = strchr(p+1, OWL_FMTEXT_UC_STARTBYTE_UTF8);
579      }
580    }
581    if (s) strcat(r,s);
582  }
583  else {
584    r = g_strdup("");
585  }
586  return r;
587}
588
589/* If in is not UTF-8, convert from ISO-8859-1. We may want to allow
590 * the caller to specify an alternative in the future. We also strip
591 * out characters in Unicode Plane 16, as we use that plane internally
592 * for formatting.
593 * Caller must free the return.
594 */
595CALLER_OWN char *owl_validate_or_convert(const char *in)
596{
597  if (g_utf8_validate(in, -1, NULL)) {
598    return owl_strip_format_chars(in);
599  }
600  else {
601    return g_convert(in, -1,
602                     "UTF-8", "ISO-8859-1",
603                     NULL, NULL, NULL);
604  }
605}
606/*
607 * Validate 'in' as UTF-8, and either return a copy of it, or an empty
608 * string if it is invalid utf-8.
609 * Caller must free the return.
610 */
611CALLER_OWN char *owl_validate_utf8(const char *in)
612{
613  char *out;
614  if (g_utf8_validate(in, -1, NULL)) {
615    out = g_strdup(in);
616  } else {
617    out = g_strdup("");
618  }
619  return out;
620}
621
622/* This is based on _extract() and _isCJ() from perl's Text::WrapI18N */
623int owl_util_can_break_after(gunichar c)
624{
625 
626  if (c == ' ') return 1;
627  if (c >= 0x3000 && c <= 0x312f) {
628    /* CJK punctuations, Hiragana, Katakana, Bopomofo */
629    if (c == 0x300a || c == 0x300c || c == 0x300e ||
630        c == 0x3010 || c == 0x3014 || c == 0x3016 ||
631        c == 0x3018 || c == 0x301a)
632      return 0;
633    return 1;
634  }
635  if (c >= 0x31a0 && c <= 0x31bf) {return 1;}  /* Bopomofo */
636  if (c >= 0x31f0 && c <= 0x31ff) {return 1;}  /* Katakana extension */
637  if (c >= 0x3400 && c <= 0x9fff) {return 1;}  /* Han Ideogram */
638  if (c >= 0xf900 && c <= 0xfaff) {return 1;}  /* Han Ideogram */
639  if (c >= 0x20000 && c <= 0x2ffff) {return 1;}  /* Han Ideogram */
640  return 0;
641}
642
643/* caller must free the return */
644CALLER_OWN char *owl_escape_highbit(const char *str)
645{
646  GString *out = g_string_new("");
647  unsigned char c;
648  while((c = (*str++))) {
649    if(c == '\\') {
650      g_string_append(out, "\\\\");
651    } else if(c & 0x80) {
652      g_string_append_printf(out, "\\x%02x", (int)c);
653    } else {
654      g_string_append_c(out, c);
655    }
656  }
657  return g_string_free(out, 0);
658}
659
660/* innards of owl_getline{,_chomp} below */
661static int owl_getline_internal(char **s, FILE *fp, int newline)
662{
663  int size = 0;
664  int target = 0;
665  int count = 0;
666  int c;
667
668  while (1) {
669    c = getc(fp);
670    if ((target + 1) > size) {
671      size += BUFSIZ;
672      *s = g_renew(char, *s, size);
673    }
674    if (c == EOF)
675      break;
676    count++;
677    if (c != '\n' || newline)
678        (*s)[target++] = c;
679    if (c == '\n')
680      break;
681  }
682  (*s)[target] = 0;
683
684  return count;
685}
686
687/* Read a line from fp, allocating memory to hold it, returning the number of
688 * byte read.  *s should either be NULL or a pointer to memory allocated with
689 * g_malloc; it will be g_renew'd as appropriate.  The caller must
690 * eventually free it.  (This is roughly the interface of getline in the gnu
691 * libc).
692 *
693 * The final newline will be included if it's there.
694 */
695int owl_getline(char **s, FILE *fp)
696{
697  return owl_getline_internal(s, fp, 1);
698}
699
700/* As above, but omitting the final newline */
701int owl_getline_chomp(char **s, FILE *fp)
702{
703  return owl_getline_internal(s, fp, 0);
704}
705
706/* Read the rest of the input available in fp into a string. */
707CALLER_OWN char *owl_slurp(FILE *fp)
708{
709  char *buf = NULL;
710  char *p;
711  int size = 0;
712  int count;
713
714  while (1) {
715    buf = g_renew(char, buf, size + BUFSIZ);
716    p = &buf[size];
717    size += BUFSIZ;
718
719    if ((count = fread(p, 1, BUFSIZ, fp)) < BUFSIZ)
720      break;
721  }
722  p[count] = 0;
723
724  return buf;
725}
726
727int owl_util_get_colorpairs(void) {
728#ifndef NCURSES_EXT_COLORS
729  /* Without ext-color support (an ABI change), ncurses only supports 256
730   * different color pairs. However, it gives us a larger number even if your
731   * ncurses is compiled without ext-color. */
732  return MIN(COLOR_PAIRS, 256);
733#else
734  /* In theory ext-color support should allow for 256 color pairs, but there's
735   * a bug in ncurses. The fix is now upstreamed. The commit which introduced
736   * this change (and simultaneously reverted the wbkgrndset change to fix the
737   * build on Solaris) should be reverted when we ship a new locker ncurses. */
738  return MIN(COLOR_PAIRS, 256);
739#endif
740}
741
742gulong owl_dirty_window_on_signal(owl_window *w, gpointer sender, const gchar *detailed_signal)
743{
744  return owl_signal_connect_object(sender, detailed_signal, G_CALLBACK(owl_window_dirty), w, G_CONNECT_SWAPPED);
745}
746
747typedef struct { /*noproto*/
748  GObject  *sender;
749  gulong    signal_id;
750} SignalData;
751
752static void _closure_invalidated(gpointer data, GClosure *closure);
753
754/*
755 * GObject's g_signal_connect_object has a documented bug. This function is
756 * identical except it does not leak the signal handler.
757 */
758gulong owl_signal_connect_object(gpointer sender, const gchar *detailed_signal, GCallback c_handler, gpointer receiver, GConnectFlags connect_flags)
759{
760  g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (sender), 0);
761  g_return_val_if_fail (detailed_signal != NULL, 0);
762  g_return_val_if_fail (c_handler != NULL, 0);
763
764  if (receiver) {
765    SignalData *sdata;
766    GClosure *closure;
767    gulong signal_id;
768
769    g_return_val_if_fail (G_IS_OBJECT (receiver), 0);
770
771    closure = ((connect_flags & G_CONNECT_SWAPPED) ? g_cclosure_new_object_swap : g_cclosure_new_object) (c_handler, receiver);
772    signal_id = g_signal_connect_closure (sender, detailed_signal, closure, connect_flags & G_CONNECT_AFTER);
773
774    /* Register the missing hooks */
775    sdata = g_slice_new0(SignalData);
776    sdata->sender = sender;
777    sdata->signal_id = signal_id;
778
779    g_closure_add_invalidate_notifier(closure, sdata, _closure_invalidated);
780
781    return signal_id;
782  } else {
783    return g_signal_connect_data(sender, detailed_signal, c_handler, NULL, NULL, connect_flags);
784  }
785}
786
787/*
788 * There are three ways the signal could come to an end:
789 *
790 * 1. The user explicitly disconnects it with the returned signal_id.
791 *    - In that case, the disconnection unref's the closure, causing it
792 *      to first be invalidated. The handler's already disconnected, so
793 *      we have no work to do.
794 * 2. The sender gets destroyed.
795 *    - GObject will disconnect each signal which then goes into the above
796 *      case. Our handler does no work.
797 * 3. The receiver gets destroyed.
798 *    - The GClosure was created by g_cclosure_new_object_{,swap} which gets
799 *      invalidated when the receiver is destroyed. We then follow through case 1
800 *      again, but *this* time, the handler has not been disconnected. We then
801 *      clean up ourselves.
802 *
803 * We can't actually hook into this process earlier with weakrefs as GObject
804 * will, on object dispose, first disconnect signals, then invalidate closures,
805 * and notify weakrefs last.
806 */
807static void _closure_invalidated(gpointer data, GClosure *closure)
808{
809  SignalData *sdata = data;
810  if (g_signal_handler_is_connected(sdata->sender, sdata->signal_id)) {
811    g_signal_handler_disconnect(sdata->sender, sdata->signal_id);
812  }
813  g_slice_free(SignalData, sdata);
814}
815
Note: See TracBrowser for help on using the repository browser.