source: trunk/gtk/main.c @ 261

Last change on this file since 261 was 261, checked in by titer, 16 years ago

Updated svn:keywords

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