source: util.c @ 95b52d1

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