source: trunk/gtk/main.c @ 249

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

Some minor code cleanups.
Handle things a little better when quitting.

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