source: trunk/gtk/main.c @ 242

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

Add IPC code for another process to communicate with a running

transmission-gtk instance.

Try to add any filenames found on the command-line, using IPC if

transmission-gtk is already running.

Some minor code cleanups.
Remove lockfile on a normal exit, justfor the sake of being tidy.

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