source: trunk/gtk/main.c @ 243

Last change on this file since 243 was 243, checked in by joshe, 16 years ago

A couple minor i18n cleanups.
Add italian translation.
Add an install make target for transmissioncli and transmission-gtk.

File size: 36.9 KB
Line 
1/*
2  Copyright (c) 2005-2006 Joshua Elsasser. All rights reserved.
3   
4  Redistribution and use in source and binary forms, with or without
5  modification, are permitted provided that the following conditions
6  are met:
7   
8   1. Redistributions of source code must retain the above copyright
9      notice, this list of conditions and the following disclaimer.
10   2. Redistributions in binary form must reproduce the above copyright
11      notice, this list of conditions and the following disclaimer in the
12      documentation and/or other materials provided with the distribution.
13   
14  THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
15  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  POSSIBILITY OF SUCH DAMAGE.
25*/
26
27#include <sys/param.h>
28#include <assert.h>
29#include <errno.h>
30#include <signal.h>
31#include <string.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <time.h>
35#include <unistd.h>
36
37#include <gtk/gtk.h>
38#include <glib/gi18n.h>
39#include <glib/gstdio.h>
40
41#include "conf.h"
42#include "dialogs.h"
43#include "ipc.h"
44#include "transmission.h"
45#include "trcellrenderertorrent.h"
46#include "util.h"
47
48#define TRACKER_EXIT_TIMEOUT    5
49
50struct cbdata {
51  tr_handle_t *tr;
52  GtkWindow *wind;
53  GtkTreeModel *model;
54  GtkTreeView *view;
55  GtkStatusbar *bar;
56  GtkWidget **buttons;
57  guint timer;
58  gboolean prefsopen;
59};
60
61struct exitdata {
62  struct cbdata *cbdata;
63  time_t started;
64  guint timer;
65};
66
67struct pieces {
68  char p[120];
69};
70
71GList *
72readargs(int argc, char **argv);
73
74void
75maketypes(void);
76gpointer
77tr_pieces_copy(gpointer);
78void
79tr_pieces_free(gpointer);
80
81void
82makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved, GList *args);
83GtkWidget *
84makewind_toolbar(struct cbdata *data);
85GtkWidget *
86makewind_list(struct cbdata *data);
87gboolean
88winclose(GtkWidget *widget, GdkEvent *event, gpointer gdata);
89gboolean
90exitcheck(gpointer gdata);
91void
92stoptransmission(tr_handle_t *tr);
93void
94setupdrag(GtkWidget *widget, struct cbdata *data);
95void
96gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
97        GtkSelectionData *sel, guint info, guint time, gpointer gdata);
98static void
99stylekludge(GObject *obj, GParamSpec *spec, gpointer gdata);
100void
101fixbuttons(GtkTreeSelection *sel, gpointer gdata);
102void
103dfname(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model,
104       GtkTreeIter *iter, gpointer gdata);
105void
106dfprog(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model,
107       GtkTreeIter *iter, gpointer gdata);
108
109gboolean
110updatemodel(gpointer gdata);
111gboolean
112listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata);
113gboolean
114listpopup(GtkWidget *widget, gpointer gdata);
115void
116dopopupmenu(GdkEventButton *event, struct cbdata *data,
117            GList *ids, int status);
118void
119killmenu(GtkWidget *menu, gpointer *gdata);
120void
121actionclick(GtkWidget *widget, gpointer gdata);
122void
123findtorrent(GtkTreeModel *model, tr_torrent_t *tor, GtkTreeIter *iter);
124gint
125intrevcmp(gconstpointer a, gconstpointer b);
126void
127doubleclick(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col,
128            gpointer gdata);
129
130gboolean
131addtorrent(void *vdata, const char *torrent, const char *dir, gboolean paused,
132           GList **errs);
133void
134addedtorrents(void *vdata);
135gboolean
136savetorrents(tr_handle_t *tr, GtkWindow *wind);
137void
138orstatus(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
139         gpointer gdata);
140void
141makeidlist(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
142           gpointer gdata);
143void
144maketorrentlist(tr_torrent_t *tor, void *data);
145void
146safepipe(void);
147void
148setupsighandlers(void);
149void
150fatalsig(int sig);
151
152#define TR_TYPE_PIECES_NAME     "tr-type-pieces"
153#define TR_TYPE_PIECES          ((const GType)tr_type_pieces)
154#define TR_PIECES(ptr)          ((struct pieces*)ptr)
155GType tr_type_pieces;
156
157#define LIST_ACTION           "torrent-list-action"
158enum listact { ACT_OPEN, ACT_START, ACT_STOP, ACT_DELETE, ACT_INFO, ACT_PREF };
159#define LIST_ACTION_FROM      "torrent-list-action-from"
160enum listfrom { FROM_BUTTON, FROM_POPUP };
161
162#define LIST_IDS              "torrent-list-ids"
163#define LIST_MENU_WIDGET      "torrent-list-popup-menu-widget"
164
165struct { const gchar *name; const gchar *id; enum listact act; gboolean nomenu;
166  int avail; const char *ttext; const char *tpriv; }
167actionitems[] = {
168  {N_("Add"),         GTK_STOCK_ADD,          ACT_OPEN,   FALSE,  0,
169   N_("Add a new torrent"), "XXX"},
170  {N_("Start"),       GTK_STOCK_EXECUTE,      ACT_START,  FALSE,
171   TR_STATUS_INACTIVE,
172   N_("Start a torrent that is not running"), "XXX"},
173  {N_("Stop"),        GTK_STOCK_STOP,         ACT_STOP,   FALSE,
174   TR_STATUS_ACTIVE,
175   N_("Stop a torrent that is running"), "XXX"},
176  {N_("Remove"),      GTK_STOCK_REMOVE,       ACT_DELETE, FALSE, ~0,
177   N_("Remove a torrent"), "XXX"},
178  {N_("Properties"),  GTK_STOCK_PROPERTIES,   ACT_INFO,   FALSE, ~0,
179   N_("Show additional information about a torrent"), "XXX"},
180  {N_("Preferences"), GTK_STOCK_PREFERENCES,  ACT_PREF,   TRUE,   0,
181   N_("Customize application behavior"), "XXX"},
182};
183
184#define CBDATA_PTR              "callback-data-pointer"
185
186#define SIGCOUNT_MAX            3
187
188static sig_atomic_t global_sigcount = 0;
189static int global_lastsig = 0;
190
191int
192main(int argc, char **argv) {
193  GtkWidget *mainwind, *preferr, *stateerr;
194  char *err;
195  tr_handle_t *tr;
196  GList *saved;
197  const char *pref;
198  long intval;
199  GList *argfiles;
200  gboolean didinit, didlock;
201
202  safepipe();
203
204  argfiles = readargs(argc, argv);
205
206  didinit = cf_init(tr_getPrefsDirectory(), NULL);
207  didlock = FALSE;
208  if(NULL != argfiles && didinit && !(didlock = cf_lock(NULL)))
209    return !ipc_sendfiles_blocking(argfiles);
210
211  setupsighandlers();
212
213  gtk_init(&argc, &argv);
214
215  bindtextdomain("transmission-gtk", LOCALEDIR);
216  bind_textdomain_codeset("transmission-gtk", "UTF-8");
217  textdomain("transmission-gtk");
218
219  g_set_application_name(_("Transmission"));
220
221  tr = tr_init();
222
223  gtk_rc_parse_string(
224    "style \"transmission-standard\" {\n"
225    " GtkDialog::action-area-border = 6\n"
226    " GtkDialog::button-spacing = 12\n"
227    " GtkDialog::content-area-border = 6\n"
228    "}\n"
229    "widget \"TransmissionDialog\" style \"transmission-standard\"\n");
230
231  if(didinit || cf_init(tr_getPrefsDirectory(), &err)) {
232    if(didlock || cf_lock(&err)) {
233      /* create main window now so any error dialogs can be it's children */
234      mainwind = gtk_window_new(GTK_WINDOW_TOPLEVEL);
235      preferr = NULL;
236      stateerr = NULL;
237
238      if(!cf_loadprefs(&err)) {
239        preferr = errmsg(GTK_WINDOW(mainwind), "%s", err);
240        g_free(err);
241      }
242      saved = cf_loadstate(&err);
243      if(NULL != err) {
244        stateerr = errmsg(GTK_WINDOW(mainwind), "%s", err);
245        g_free(err);
246      }
247
248      /* set the upload limit */
249      setlimit(tr);
250
251      /* set the listening port */
252      if(NULL != (pref = cf_getpref(PREF_PORT)) &&
253         0 < (intval = strtol(pref, NULL, 10)) && 0xffff >= intval)
254        tr_setBindPort(tr, intval);
255
256      maketypes();
257      makewind(mainwind, tr, saved, argfiles);
258
259      if(NULL != preferr)
260        gtk_widget_show_all(preferr);
261      if(NULL != stateerr)
262        gtk_widget_show_all(stateerr);
263    } else {
264      gtk_widget_show(errmsg_full(NULL, (callbackfunc_t)gtk_main_quit,
265                                  NULL, "%s", err));
266      g_free(err);
267    }
268  } else {
269    gtk_widget_show(errmsg_full(NULL, (callbackfunc_t)gtk_main_quit,
270                                NULL, "%s", err));
271    g_free(err);
272  }
273
274  if(NULL != argfiles)
275    freestrlist(argfiles);
276
277  gtk_main();
278
279  return 0;
280}
281
282GList *
283readargs(int argc, char **argv) {
284  while(0 < --argc) {
285    argv++;
286    if(0 == strcmp("--", *argv))
287      return checkfilenames(argc - 1, argv + 1);
288    else if('-' != argv[0][0])
289      return checkfilenames(argc, argv);
290  }
291
292  return NULL;
293}
294
295void
296maketypes(void) {
297  tr_type_pieces = g_boxed_type_register_static(
298    TR_TYPE_PIECES_NAME, tr_pieces_copy, tr_pieces_free);
299}
300
301gpointer
302tr_pieces_copy(gpointer data) {
303  return g_memdup(data, sizeof(struct pieces));
304}
305
306void
307tr_pieces_free(gpointer data) {
308  g_free(data);
309}
310
311void
312makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved, GList *args) {
313  GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
314  GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
315  GtkWidget *status = gtk_statusbar_new();
316  struct cbdata *data = g_new0(struct cbdata, 1);
317  GtkWidget *list;
318  GtkRequisition req;
319  GList *loaderrs, *ii;
320  struct cf_torrentstate *ts;
321  gint height;
322  char *str;
323
324  data->tr = tr;
325  data->wind = GTK_WINDOW(wind);
326  data->timer = -1;
327  /* filled in by makewind_list */
328  data->model = NULL;
329  data->view = NULL;
330  data->bar = GTK_STATUSBAR(status);
331  data->buttons = NULL;
332  data->prefsopen = FALSE;
333
334  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER,
335                                 GTK_POLICY_AUTOMATIC);
336
337  gtk_box_pack_start(GTK_BOX(vbox), makewind_toolbar(data), FALSE, FALSE, 0);
338
339  list = makewind_list(data);
340  gtk_container_add(GTK_CONTAINER(scroll), list);
341  gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
342
343  gtk_statusbar_push(GTK_STATUSBAR(status), 0, "");
344  gtk_box_pack_start(GTK_BOX(vbox), status, FALSE, FALSE, 0);
345
346  gtk_container_add(GTK_CONTAINER(wind), vbox);
347  gtk_window_set_title(data->wind, g_get_application_name());
348  g_signal_connect(G_OBJECT(wind), "delete_event", G_CALLBACK(winclose), data);
349
350  setupdrag(list, data);
351
352  loaderrs = NULL;
353  for(ii = g_list_first(saved); NULL != ii; ii = ii->next) {
354    ts = ii->data;
355    addtorrent(data, ts->ts_torrent, ts->ts_directory, ts->ts_paused,
356               &loaderrs);
357    cf_freestate(ts);
358  }
359  g_list_free(saved);
360
361  for(ii = g_list_first(args); NULL != ii; ii = ii->next)
362    addtorrent(data, ii->data, NULL, FALSE, &loaderrs);
363
364  if(NULL != loaderrs) {
365    str = joinstrlist(loaderrs, "\n");
366    errmsg(GTK_WINDOW(wind), ngettext("Failed to load the torrent file %s",
367                                      "Failed to load the torrent files:\n%s",
368                                      g_list_length(loaderrs)), str);
369    g_list_foreach(loaderrs, (GFunc)g_free, NULL);
370    g_list_free(loaderrs);
371    g_free(str);
372    savetorrents(tr, GTK_WINDOW(wind));
373  }
374
375  data->timer = g_timeout_add(500, updatemodel, data);
376  updatemodel(data);
377
378  gtk_widget_show_all(vbox);
379  gtk_widget_realize(wind);
380
381  gtk_widget_size_request(list, &req);
382  height = req.height;
383  gtk_widget_size_request(scroll, &req);
384  height -= req.height;
385  gtk_widget_size_request(wind, &req);
386  height += req.height;
387  gtk_window_set_default_size(GTK_WINDOW(wind), -1, (height > req.width ?
388     MIN(height, req.width * 8 / 5) : MAX(height, req.width * 5 / 8)));
389
390  gtk_widget_show(wind);
391
392  ipc_socket_setup(GTK_WINDOW(wind), addtorrent, addedtorrents, data);
393}
394
395GtkWidget *
396makewind_toolbar(struct cbdata *data) {
397  GtkWidget *bar = gtk_toolbar_new();
398  GtkToolItem *item;
399  unsigned int ii;
400
401  gtk_toolbar_set_tooltips(GTK_TOOLBAR(bar), TRUE);
402  gtk_toolbar_set_show_arrow(GTK_TOOLBAR(bar), FALSE);
403  gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_BOTH);
404
405  data->buttons = g_new(GtkWidget*, ALEN(actionitems));
406
407  for(ii = 0; ii < ALEN(actionitems); ii++) {
408    item = gtk_tool_button_new_from_stock(actionitems[ii].id);
409    data->buttons[ii] = GTK_WIDGET(item);
410    gtk_tool_button_set_label(GTK_TOOL_BUTTON(item),
411                              gettext(actionitems[ii].name));
412    gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(item), GTK_TOOLBAR(bar)->tooltips,
413                              gettext(actionitems[ii].ttext),
414                              actionitems[ii].tpriv);
415    g_object_set_data(G_OBJECT(item), LIST_ACTION,
416                      GINT_TO_POINTER(actionitems[ii].act));
417    g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
418                      GINT_TO_POINTER(FROM_BUTTON));
419    g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(actionclick), data);
420    gtk_toolbar_insert(GTK_TOOLBAR(bar), item, -1);
421  }
422
423  return bar;
424}
425
426/* XXX check for unused data in model */
427enum {MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_TERR, MC_PROG, MC_DRATE, MC_URATE,
428      MC_ETA, MC_PEERS, MC_UPEERS, MC_DPEERS, MC_PIECES, MC_DOWN, MC_UP,
429      MC_TORRENT, MC_ROW_COUNT};
430
431GtkWidget *
432makewind_list(struct cbdata *data) {
433  GType types[] = {
434    /* info->name, info->totalSize, status,     error,      trackerError, */
435    G_TYPE_STRING, G_TYPE_UINT64,   G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING,
436    /* progress,  rateDownload, rateUpload,   eta,        peersTotal, */
437    G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT,
438    /* peersUploading, peersDownloading, pieces,         downloaded, */
439    G_TYPE_INT,        G_TYPE_INT,       TR_TYPE_PIECES, G_TYPE_UINT64,
440    /* uploaded,   the handle for the torrent */
441    G_TYPE_UINT64, G_TYPE_POINTER};
442  GtkListStore *store;
443  GtkWidget *view;
444  GtkTreeViewColumn *col;
445  GtkTreeSelection *sel;
446  GtkCellRenderer *namerend, *progrend;
447  char *str;
448
449  assert(MC_ROW_COUNT == ALEN(types));
450
451  store = gtk_list_store_newv(MC_ROW_COUNT, types);
452  view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
453  /* XXX do I need to worry about reference counts anywhere else? */
454  g_object_unref(G_OBJECT(store));
455  data->model = GTK_TREE_MODEL(store);
456  data->view = GTK_TREE_VIEW(view);
457
458  namerend = gtk_cell_renderer_text_new();
459  col = gtk_tree_view_column_new_with_attributes(_("Name"), namerend, NULL);
460  gtk_tree_view_column_set_cell_data_func(col, namerend, dfname, NULL, NULL);
461  gtk_tree_view_column_set_expand(col, TRUE);
462  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
463
464  progrend = tr_cell_renderer_torrent_new();
465  /* this string is only used to determing the size of the progress bar */
466  str = g_markup_printf_escaped("<big>%s</big>", _("  fnord    fnord  "));
467  g_object_set(progrend, "label", str, NULL);
468  g_free(str);
469  col = gtk_tree_view_column_new_with_attributes(_("Progress"), progrend, NULL);
470  gtk_tree_view_column_set_cell_data_func(col, progrend, dfprog, NULL, NULL);
471  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
472
473  /* XXX this shouldn't be necessary */
474  g_signal_connect(view, "notify", G_CALLBACK(stylekludge), progrend);
475
476  gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
477  sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
478  gtk_tree_selection_set_mode(GTK_TREE_SELECTION(sel), GTK_SELECTION_MULTIPLE);
479  g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(fixbuttons), data);
480  g_signal_connect(G_OBJECT(view), "button-press-event",
481                   G_CALLBACK(listclick), data);
482  g_signal_connect(G_OBJECT(view), "popup-menu", G_CALLBACK(listpopup), data);
483  g_signal_connect(G_OBJECT(view), "row-activated",
484                   G_CALLBACK(doubleclick), data);
485  gtk_widget_show_all(view);
486
487  return view;
488}
489
490gboolean
491winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
492  struct cbdata *data = gdata;
493  struct exitdata *edata;
494  tr_stat_t *st;
495  GtkTreeIter iter;
496  tr_torrent_t *tor;
497  gboolean going;
498
499  if(0 >= data->timer)
500    g_source_remove(data->timer);
501  data->timer = -1;
502
503  going = gtk_tree_model_get_iter_first(data->model, &iter);
504  while(going) {
505    gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
506    st = tr_torrentStat(tor);
507    if(TR_STATUS_ACTIVE & st->status) {
508      tr_torrentStop(tor);
509      going = gtk_tree_model_iter_next(data->model, &iter);
510    } else {
511      tr_torrentClose(data->tr, tor);
512      going = gtk_list_store_remove(GTK_LIST_STORE(data->model), &iter);
513    }
514  }
515
516  /* XXX should disable widgets or something */
517
518  /* try to wait until torrents stop before exiting */
519  edata = g_new0(struct exitdata, 1);
520  edata->cbdata = data;
521  edata->started = time(NULL);
522  edata->timer = g_timeout_add(500, exitcheck, edata);
523
524  /* returning FALSE means to destroy the window */
525  return TRUE;
526}
527
528gboolean
529exitcheck(gpointer gdata) {
530  struct exitdata *data = gdata;
531  tr_stat_t *st;
532  GtkTreeIter iter;
533  tr_torrent_t *tor;
534  gboolean go;
535
536  go = gtk_tree_model_get_iter_first(data->cbdata->model, &iter);
537  while(go) {
538    gtk_tree_model_get(data->cbdata->model, &iter, MC_TORRENT, &tor, -1);
539    st = tr_torrentStat(tor);
540    if(!(TR_STATUS_PAUSE & st->status))
541      go = gtk_tree_model_iter_next(data->cbdata->model, &iter);
542    else {
543      tr_torrentClose(data->cbdata->tr, tor);
544      go = gtk_list_store_remove(GTK_LIST_STORE(data->cbdata->model), &iter);
545    }
546  }
547
548  /* keep going if we still have torrents and haven't hit the exit timeout */
549  if(0 < tr_torrentCount(data->cbdata->tr) &&
550     time(NULL) - data->started < TRACKER_EXIT_TIMEOUT) {
551    assert(gtk_tree_model_get_iter_first(data->cbdata->model, &iter));
552    updatemodel(data->cbdata);
553    return TRUE;
554  }
555
556  /* exit otherwise */
557
558  if(0 >= data->timer)
559    g_source_remove(data->timer);
560  data->timer = -1;
561
562  stoptransmission(data->cbdata->tr);
563
564  gtk_widget_destroy(GTK_WIDGET(data->cbdata->wind));
565  g_free(data->cbdata);
566  g_free(data);
567  gtk_main_quit();
568
569  return FALSE;
570}
571
572void
573stoptransmission(tr_handle_t *tr) {
574  GList *list, *ii;
575
576  list = NULL;
577  tr_torrentIterate(tr, maketorrentlist, &list);
578  for(ii = g_list_first(list); NULL != ii; ii = ii->next)
579    tr_torrentClose(tr, ii->data);
580  g_list_free(list);
581
582  tr_close(tr);
583}
584
585void
586gotdrag(GtkWidget *widget SHUTUP, GdkDragContext *dc, gint x SHUTUP,
587        gint y SHUTUP, GtkSelectionData *sel, guint info SHUTUP, guint time,
588        gpointer gdata) {
589  struct cbdata *data = gdata;
590  char prefix[] = "file:";
591  char *files, *decoded, *deslashed, *hostless;
592  int ii, len;
593  GList *errs;
594  gboolean gotfile;
595  struct stat sb;
596  int prelen = strlen(prefix);
597
598#ifdef DND_DEBUG
599  char *sele = gdk_atom_name(sel->selection);
600  char *targ = gdk_atom_name(sel->target);
601  char *type = gdk_atom_name(sel->type);
602
603  fprintf(stderr, "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i\n",
604          sele, targ, type, sel->format, sel->length);
605  g_free(sele);
606  g_free(targ);
607  g_free(type);
608  if(8 == sel->format) {
609    for(ii = 0; ii < sel->length; ii++)
610      fprintf(stderr, "%02X ", sel->data[ii]);
611    fprintf(stderr, "\n");
612  }
613#endif
614
615  errs = NULL;
616  gotfile = FALSE;
617  if(gdk_atom_intern("XdndSelection", FALSE) == sel->selection &&
618     8 == sel->format) {
619    /* split file list on carriage returns and linefeeds */
620    files = g_new(char, sel->length + 1);
621    memcpy(files, sel->data, sel->length);
622    files[sel->length] = '\0';
623    for(ii = 0; '\0' != files[ii]; ii++)
624      if('\015' == files[ii] || '\012' == files[ii])
625        files[ii] = '\0';
626
627    /* try to get a usable filename out of the URI supplied and add it */
628    for(ii = 0; ii < sel->length; ii += len + 1) {
629      if('\0' == files[ii])
630        len = 0;
631      else {
632        len = strlen(files + ii);
633        /* de-urlencode the URI */
634        decoded = urldecode(files + ii, len);
635        if(g_utf8_validate(decoded, -1, NULL)) {
636          /* remove the file: prefix */
637          if(prelen < len && 0 == strncmp(prefix, decoded, prelen)) {
638            deslashed = decoded + prelen;
639            /* trim excess / characters from the beginning */
640            while('/' == deslashed[0] && '/' == deslashed[1])
641              deslashed++;
642            /* if the file doesn't exist, the first part might be a hostname */
643            if(0 > g_stat(deslashed, &sb) &&
644               NULL != (hostless = strchr(deslashed + 1, '/')) &&
645               0 == g_stat(hostless, &sb))
646              deslashed = hostless;
647            /* finally, try to add it as a torrent */
648            if(addtorrent(data, deslashed, NULL, FALSE, &errs))
649              gotfile = TRUE;
650          }
651        }
652        g_free(decoded);
653      }
654    }
655
656    g_free(files);
657    if(gotfile)
658      addedtorrents(data);
659
660    /* print any errors */
661    if(NULL != errs) {
662      files = joinstrlist(errs, "\n");
663      errmsg(data->wind, ngettext("Failed to load the torrent file %s",
664                                  "Failed to load the torrent files:\n%s",
665                                  g_list_length(errs)), files);
666      g_list_foreach(errs, (GFunc)g_free, NULL);
667      g_list_free(errs);
668      g_free(files);
669    }
670  }
671
672  gtk_drag_finish(dc, gotfile, FALSE, time);
673}
674
675void
676setupdrag(GtkWidget *widget, struct cbdata *data) {
677  GtkTargetEntry targets[] = {
678    { "STRING",     0, 0 },
679    { "text/plain", 0, 0 },
680    { "text/uri-list", 0, 0 },
681  };
682
683  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
684
685  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
686                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
687}
688
689/* kludge to have the progress bars notice theme changes */
690static void
691stylekludge(GObject *obj, GParamSpec *spec, gpointer gdata) {
692  if(0 == strcmp("style", spec->name)) {
693    tr_cell_renderer_torrent_reset_style(TR_CELL_RENDERER_TORRENT(gdata));
694    gtk_widget_queue_draw(GTK_WIDGET(obj));
695  }
696}
697
698/* disable buttons the user shouldn't be able to click on */
699void
700fixbuttons(GtkTreeSelection *sel, gpointer gdata) {
701  struct cbdata *data = gdata;
702  gboolean selected;
703  unsigned int ii;
704  int status;
705
706  if(NULL == sel)
707    sel = gtk_tree_view_get_selection(data->view);
708  status = 0;
709  gtk_tree_selection_selected_foreach(sel, orstatus, &status);
710  selected = (0 < gtk_tree_selection_count_selected_rows(sel));
711
712  for(ii = 0; ii < ALEN(actionitems); ii++)
713    if(actionitems[ii].avail)
714      gtk_widget_set_sensitive(data->buttons[ii],
715                               (selected && (actionitems[ii].avail & status)));
716}
717
718void
719dfname(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
720       GtkTreeModel *model, GtkTreeIter *iter, gpointer gdata SHUTUP) {
721  char *name, *mb, *terr, *str, *top, *bottom;
722  guint64 size;
723  gfloat prog;
724  int status, err, eta, tpeers, upeers, dpeers;
725
726  gtk_tree_model_get(model, iter, MC_NAME, &name, MC_STAT, &status,
727    MC_ERR, &err, MC_SIZE, &size, MC_PROG, &prog, MC_ETA, &eta,
728    MC_PEERS, &tpeers, MC_UPEERS, &upeers, MC_DPEERS, &dpeers, -1);
729
730  if(0 > tpeers)
731    tpeers = 0;
732  if(0 > upeers)
733    upeers = 0;
734  if(0 > dpeers)
735    dpeers = 0;
736  mb = readablesize(size);
737  prog *= 100;
738
739  if(status & TR_STATUS_CHECK)
740    top = g_strdup_printf(_("Checking existing files (%.1f%%)"), prog);
741  else if(status & TR_STATUS_DOWNLOAD) {
742    if(0 > eta)
743      top = g_strdup_printf(_("Finishing in --:--:-- (%.1f%%)"), prog);
744    else
745      top = g_strdup_printf(_("Finishing in %02i:%02i:%02i (%.1f%%)"),
746                            eta / 60 / 60, eta / 60 % 60, eta % 60, prog);
747  }
748  else if(status & TR_STATUS_SEED)
749    top = g_strdup_printf(ngettext("Seeding, uploading to %d of %d peer",
750                                   "Seeding, uploading to %d of %d peers",
751                                   tpeers), dpeers, tpeers);
752  else if(status & TR_STATUS_STOPPING)
753    top = g_strdup(_("Stopping..."));
754  else if(status & TR_STATUS_PAUSE)
755    top = g_strdup_printf(_("Stopped (%.1f%%)"), prog);
756  else {
757    top = g_strdup("");
758    assert("XXX unknown status");
759  }
760
761  if(TR_NOERROR != err) {
762    gtk_tree_model_get(model, iter, MC_TERR, &terr, -1);
763    bottom = g_strconcat(_("Error: "), terr, NULL);
764    g_free(terr);
765  }
766  else if(status & TR_STATUS_DOWNLOAD)
767    bottom = g_strdup_printf(ngettext("Downloading from %i of %i peer",
768                                      "Downloading from %i of %i peers",
769                                      tpeers), upeers, tpeers);
770  else
771    bottom = NULL;
772
773  str = g_markup_printf_escaped("<big>%s (%s)</big>\n<small>%s\n%s</small>",
774                                name, mb, top, (NULL == bottom ? "" : bottom));
775  g_object_set(rend, "markup", str, NULL);
776  g_free(name);
777  g_free(mb);
778  g_free(str);
779  g_free(top);
780  g_free(bottom);
781}
782
783void
784dfprog(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
785       GtkTreeModel *model, GtkTreeIter *iter, gpointer gdata SHUTUP) {
786  char *dlstr, *ulstr, *str, *marked;
787  gfloat prog, dl, ul;
788  guint64 down, up;
789
790  gtk_tree_model_get(model, iter, MC_PROG, &prog, MC_DRATE, &dl, MC_URATE, &ul,
791                     MC_DOWN, &down, MC_UP, &up, -1);
792  if(0.0 > prog)
793    prog = 0.0;
794  else if(1.0 < prog)
795    prog = 1.0;
796
797  ulstr = readablesize(ul * 1024.0);
798  if(1.0 == prog) {
799    dlstr = ratiostr(down, up);
800    str = g_strdup_printf(_("Ratio: %s\nUL: %s/s"), dlstr, ulstr);
801  } else {
802    dlstr = readablesize(dl * 1024.0);
803    str = g_strdup_printf(_("DL: %s/s\nUL: %s/s"), dlstr, ulstr);
804  }
805  marked = g_markup_printf_escaped("<small>%s</small>", str);
806  g_object_set(rend, "text", str, "value", prog, NULL);
807  g_free(dlstr);
808  g_free(ulstr);
809  g_free(str);
810  g_free(marked);
811}
812
813gboolean
814updatemodel(gpointer gdata) {
815  struct cbdata *data = gdata;
816  tr_torrent_t *tor;
817  tr_stat_t *st;
818  tr_info_t *in;
819  GtkTreeIter iter;
820  float up, down;
821  char *upstr, *downstr, *str;
822
823  if(0 < global_sigcount) {
824    stoptransmission(data->tr);
825    global_sigcount = SIGCOUNT_MAX;
826    raise(global_lastsig);
827  }
828
829  if(gtk_tree_model_get_iter_first(data->model, &iter)) {
830    do {
831      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
832      st = tr_torrentStat(tor);
833      in = tr_torrentInfo(tor);
834      /* XXX find out if setting the same data emits changed signal */
835      gtk_list_store_set(GTK_LIST_STORE(data->model), &iter, MC_NAME, in->name,
836        MC_SIZE, in->totalSize, MC_STAT, st->status, MC_ERR, st->error,
837        MC_TERR, st->trackerError, MC_PROG, st->progress,
838        MC_DRATE, st->rateDownload, MC_URATE, st->rateUpload, MC_ETA, st->eta,
839        MC_PEERS, st->peersTotal, MC_UPEERS, st->peersUploading,
840        MC_DPEERS, st->peersDownloading, MC_DOWN, st->downloaded,
841        MC_UP, st->uploaded, -1);
842    } while(gtk_tree_model_iter_next(data->model, &iter));
843  }
844
845  /* update the status bar */
846  tr_torrentRates(data->tr, &up, &down);
847  downstr = readablesize(down * 1024.0);
848  upstr = readablesize(up * 1024.0);
849  str = g_strdup_printf(_("     Total DL: %s/s     Total UL: %s/s"),
850                        upstr, downstr);
851  gtk_statusbar_pop(data->bar, 0);
852  gtk_statusbar_push(data->bar, 0, str);
853  g_free(str);
854  g_free(upstr);
855  g_free(downstr);
856
857  /* the status of the selected item may have changed, so update the buttons */
858  fixbuttons(NULL, data);
859
860  return TRUE;
861}
862
863/* show a popup menu for a right-click on the list */
864gboolean
865listclick(GtkWidget *widget SHUTUP, GdkEventButton *event, gpointer gdata) {
866  struct cbdata *data = gdata;
867  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
868  GtkTreePath *path;
869  GtkTreeIter iter;
870  int status;
871  gpointer tor;
872  GList *ids;
873
874  if(GDK_BUTTON_PRESS == event->type && 3 == event->button) {
875    /* find what row, if any, the user clicked on */
876    if(!gtk_tree_view_get_path_at_pos(data->view, event->x, event->y, &path,
877                                      NULL, NULL, NULL))
878      /* no row was clicked, do the popup with no torrent IDs or status */
879      dopopupmenu(event, data, NULL, 0);
880    else {
881      if(gtk_tree_model_get_iter(data->model, &iter, path)) {
882        /* get ID and status for the right-clicked row */
883        gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor,
884                           MC_STAT, &status, -1);
885        /* get a list of selected IDs */
886        ids = NULL;
887        gtk_tree_selection_selected_foreach(sel, makeidlist, &ids);
888        /* is the clicked row selected? */
889        if(NULL == g_list_find(ids, tor)) {
890          /* no, do the popup for just the clicked row */
891          g_list_free(ids);
892          dopopupmenu(event, data, g_list_append(NULL, tor), status);
893        } else {
894          /* yes, do the popup for all the selected rows */
895          gtk_tree_selection_selected_foreach(sel, orstatus, &status);
896          dopopupmenu(event, data, ids, status);
897        }
898      }
899      gtk_tree_path_free(path);
900    }
901    return TRUE;
902  }
903
904  return FALSE;
905}
906
907gboolean
908listpopup(GtkWidget *widget SHUTUP, gpointer gdata) {
909  struct cbdata *data = gdata;
910  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
911  GList *ids;
912  int status;
913
914  if(0 >= gtk_tree_selection_count_selected_rows(sel))
915    dopopupmenu(NULL, data, NULL, 0);
916  else {
917    status = 0;
918    gtk_tree_selection_selected_foreach(sel, orstatus, &status);
919    ids = NULL;
920    gtk_tree_selection_selected_foreach(sel, makeidlist, &ids);
921    dopopupmenu(NULL, data, ids, status);
922  }
923
924  return TRUE;
925}
926
927void
928dopopupmenu(GdkEventButton *event, struct cbdata *data,
929            GList *ids, int status) {
930  GtkWidget *menu = gtk_menu_new();
931  GtkWidget *item;
932  unsigned int ii;
933
934  for(ii = 0; ii < ALEN(actionitems); ii++) {
935    if(actionitems[ii].nomenu ||
936       (actionitems[ii].avail &&
937        (NULL == ids || !(actionitems[ii].avail & status))))
938      continue;
939    item = gtk_menu_item_new_with_label(gettext(actionitems[ii].name));
940    /* set the action for the menu item */
941    g_object_set_data(G_OBJECT(item), LIST_ACTION,
942                      GINT_TO_POINTER(actionitems[ii].act));
943    /* show that this action came from a popup menu */
944    g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
945                      GINT_TO_POINTER(FROM_POPUP));
946    /* set a glist of selected torrent's IDs */
947    g_object_set_data(G_OBJECT(item), LIST_IDS, ids);
948    /* set the menu widget, so the activate handler can destroy it */
949    g_object_set_data(G_OBJECT(item), LIST_MENU_WIDGET, menu);
950    g_signal_connect(G_OBJECT(item), "activate",
951                     G_CALLBACK(actionclick), data);
952    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
953  }
954
955  /* set up the glist to be freed when the menu is destroyed */
956  g_object_set_data_full(G_OBJECT(menu), LIST_IDS, ids,
957                         (GDestroyNotify)g_list_free);
958
959  /* destroy the menu if the user doesn't select anything */
960  g_signal_connect(menu, "selection-done", G_CALLBACK(killmenu), NULL);
961
962  gtk_widget_show_all(menu);
963
964  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
965                 (NULL == event ? 0 : event->button),
966                 gdk_event_get_time((GdkEvent*)event));
967}
968
969void
970killmenu(GtkWidget *menu, gpointer *gdata SHUTUP) {
971  gtk_widget_destroy(menu);
972}
973
974void
975actionclick(GtkWidget *widget, gpointer gdata) {
976  struct cbdata *data = gdata;
977  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
978  enum listact act =
979    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION));
980  enum listfrom from =
981    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION_FROM));
982  unsigned int actindex;
983  tr_stat_t *sb;
984  GList *ids, *ii;
985  tr_torrent_t *tor;
986  gboolean updatesave;
987  GtkTreeIter iter;
988
989  /* destroy the popup menu, if any */
990  if(FROM_POPUP == from)
991    gtk_widget_destroy(g_object_get_data(G_OBJECT(widget), LIST_MENU_WIDGET));
992
993  switch(act) {
994    case ACT_OPEN:
995      makeaddwind(data->wind, addtorrent, addedtorrents, data);
996      return;
997    case ACT_PREF:
998      if(!data->prefsopen)
999        makeprefwindow(data->wind, data->tr, &data->prefsopen);
1000      return;
1001    default:
1002      break;
1003  }
1004
1005  switch(from) {
1006    case FROM_BUTTON:
1007      ids = NULL;
1008      gtk_tree_selection_selected_foreach(sel, makeidlist, &ids);
1009      break;
1010    case FROM_POPUP:
1011      ids = g_object_get_data(G_OBJECT(widget), LIST_IDS);
1012      break;
1013    default:
1014      assert(!"unknown action source");
1015      break;
1016  }
1017
1018  for(actindex = 0; actindex < ALEN(actionitems); actindex++)
1019    if(actionitems[actindex].act == act)
1020      break;
1021  assert(actindex < ALEN(actionitems));
1022
1023  updatesave = FALSE;
1024
1025  for(ii = g_list_sort(ids, intrevcmp); NULL != ii; ii = ii->next) {
1026    tor = ii->data;
1027    sb = tr_torrentStat(tor);
1028
1029    /* check if this action is valid for this torrent */
1030    if(actionitems[actindex].nomenu ||
1031       (actionitems[actindex].avail &&
1032        !(actionitems[actindex].avail & sb->status)))
1033      continue;
1034
1035    switch(act) {
1036      case ACT_START:
1037        tr_torrentStart(tor);
1038        updatesave = TRUE;
1039        break;
1040      case ACT_STOP:
1041        tr_torrentStop(tor);
1042        updatesave = TRUE;
1043        break;
1044      case ACT_DELETE:
1045        if(TR_STATUS_ACTIVE & sb->status)
1046          tr_torrentStop(tor);
1047        tr_torrentClose(data->tr, tor);
1048        findtorrent(data->model, tor, &iter);
1049        gtk_list_store_remove(GTK_LIST_STORE(data->model), &iter);
1050        updatesave = TRUE;
1051        /* XXX should only unselect deleted rows */
1052        gtk_tree_selection_unselect_all(
1053          gtk_tree_view_get_selection(data->view));
1054        break;
1055      case ACT_INFO:
1056        makeinfowind(data->wind, tor);
1057        break;
1058      default:
1059        assert(!"unknown type");
1060        break;
1061    }
1062  }
1063
1064  if(updatesave) {
1065    savetorrents(data->tr, data->wind);
1066    updatemodel(data);
1067  }
1068
1069  if(FROM_BUTTON == from)
1070    g_list_free(ids);
1071}
1072
1073void
1074findtorrent(GtkTreeModel *model, tr_torrent_t *tor, GtkTreeIter *iter) {
1075  gpointer ptr;
1076
1077  if(gtk_tree_model_get_iter_first(model, iter)) {
1078    do {
1079      gtk_tree_model_get(model, iter, MC_TORRENT, &ptr, -1);
1080      if(tor == ptr)
1081        return;
1082    } while(gtk_tree_model_iter_next(model, iter));
1083  }
1084
1085  assert(!"torrent not found");
1086}
1087
1088gint
1089intrevcmp(gconstpointer a, gconstpointer b) {
1090  int aint = GPOINTER_TO_INT(a);
1091  int bint = GPOINTER_TO_INT(b);
1092
1093  if(bint > aint)
1094    return 1;
1095  else if(bint < aint)
1096    return -1;
1097  else
1098    return 0;
1099}
1100
1101void
1102doubleclick(GtkWidget *widget SHUTUP, GtkTreePath *path,
1103            GtkTreeViewColumn *col SHUTUP, gpointer gdata) {
1104  struct cbdata *data = gdata;
1105  GtkTreeIter iter;
1106  tr_torrent_t *tor;
1107
1108  if(gtk_tree_model_get_iter(data->model, &iter, path)) {
1109    gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
1110    makeinfowind(data->wind, tor);
1111  }
1112}
1113
1114gboolean
1115addtorrent(void *vdata, const char *torrent, const char *dir, gboolean paused,
1116           GList **errs) {
1117  const struct { const int err; const char *msg; } errstrs[] = {
1118    {TR_EINVALID,       N_("not a valid torrent file")},
1119    {TR_EDUPLICATE,     N_("torrent is already open")},
1120  };
1121  struct cbdata *data = vdata;
1122  tr_torrent_t *new;
1123  char *wd;
1124  int err;
1125  unsigned int ii;
1126  GtkTreeIter iter;
1127
1128  if(NULL == dir && NULL != (dir = cf_getpref(PREF_DIR))) {
1129    if(!mkdir_p(dir, 0777)) {
1130      errmsg(data->wind, _("Failed to create the directory %s:\n%s"),
1131             dir, strerror(errno));
1132      return FALSE;
1133    }
1134  }
1135
1136  if(NULL == (new = tr_torrentInit(data->tr, torrent, &err))) {
1137    for(ii = 0; ii < ALEN(errstrs); ii++)
1138      if(err == errstrs[ii].err)
1139        break;
1140    if(NULL == errs) {
1141      if(ii == ALEN(errstrs))
1142        errmsg(data->wind, _("Failed to load the torrent file %s"), torrent);
1143      else
1144        errmsg(data->wind, _("Failed to load the torrent file %s: %s"),
1145               torrent, gettext(errstrs[ii].msg));
1146    } else {
1147      if(ii == ALEN(errstrs))
1148        *errs = g_list_append(*errs, g_strdup(torrent));
1149      else
1150        *errs = g_list_append(*errs, g_strdup_printf(_("%s (%s)"),
1151                              torrent, gettext(errstrs[ii].msg)));
1152    }
1153    return FALSE;
1154  }
1155
1156  gtk_list_store_append(GTK_LIST_STORE(data->model), &iter);
1157  gtk_list_store_set(GTK_LIST_STORE(data->model), &iter, MC_TORRENT, new, -1);
1158
1159  if(NULL != dir)
1160    tr_torrentSetFolder(new, dir);
1161  else {
1162    wd = g_new(char, MAXPATHLEN + 1);
1163    if(NULL == getcwd(wd, MAXPATHLEN + 1))
1164      tr_torrentSetFolder(new, ".");
1165    else {
1166      tr_torrentSetFolder(new, wd);
1167      free(wd);
1168    }
1169  }
1170
1171  if(!paused)
1172    tr_torrentStart(new);
1173
1174  return TRUE;
1175}
1176
1177void
1178addedtorrents(void *vdata) {
1179  struct cbdata *data = vdata;
1180
1181  updatemodel(data);
1182  savetorrents(data->tr, data->wind);
1183}
1184
1185gboolean
1186savetorrents(tr_handle_t *tr, GtkWindow *wind) {
1187  GList *torrents;
1188  char *errstr;
1189  gboolean ret;
1190
1191  torrents = NULL;
1192  tr_torrentIterate(tr, maketorrentlist, &torrents);
1193
1194  if(!(ret = cf_savestate(torrents, &errstr))) {
1195    errmsg(wind, "%s", errstr);
1196    g_free(errstr);
1197  }
1198
1199  g_list_free(torrents);
1200
1201  return ret;
1202}
1203
1204/* use with gtk_tree_selection_selected_foreach to | status of selected rows */
1205void
1206orstatus(GtkTreeModel *model, GtkTreePath *path SHUTUP, GtkTreeIter *iter,
1207         gpointer gdata) {
1208  int *allstatus = gdata;
1209  int status;
1210
1211  gtk_tree_model_get(model, iter, MC_STAT, &status, -1);
1212  *allstatus |= status;
1213}
1214
1215void
1216makeidlist(GtkTreeModel *model, GtkTreePath *path SHUTUP, GtkTreeIter *iter,
1217           gpointer gdata) {
1218  GList **ids = gdata;
1219  gpointer ptr;
1220
1221  gtk_tree_model_get(model, iter, MC_TORRENT, &ptr, -1);
1222  *ids = g_list_append(*ids, ptr);
1223}
1224
1225void
1226maketorrentlist(tr_torrent_t *tor, void *data) {
1227  GList **list = data;
1228
1229  *list = g_list_append(*list, tor);
1230}
1231
1232void
1233safepipe(void) {
1234  struct sigaction sa;
1235
1236  bzero(&sa, sizeof(sa));
1237  sa.sa_handler = SIG_IGN;
1238  sigaction(SIGPIPE, &sa, NULL);
1239}
1240
1241void
1242setupsighandlers(void) {
1243  int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2};
1244  struct sigaction sa;
1245  unsigned int ii;
1246
1247  bzero(&sa, sizeof(sa));
1248  sa.sa_handler = fatalsig;
1249  for(ii = 0; ii < ALEN(sigs); ii++)
1250    sigaction(sigs[ii], &sa, NULL);
1251}
1252
1253void
1254fatalsig(int sig) {
1255  struct sigaction sa;
1256
1257  global_lastsig = sig;
1258
1259  if(SIGCOUNT_MAX <= ++global_sigcount) {
1260    bzero(&sa, sizeof(sa));
1261    sa.sa_handler = SIG_DFL;
1262    sigaction(sig, &sa, NULL);
1263    raise(sig);
1264  }
1265}
Note: See TracBrowser for help on using the repository browser.