source: branches/2.7x/gtk/main.c @ 13953

Last change on this file since 13953 was 13953, checked in by jordan, 9 years ago

(2.7x) backport r13952 to fix bug #5271 in 2.7x

  • Property svn:keywords set to Date Rev Author Id
File size: 49.7 KB
Line 
1/******************************************************************************
2 * $Id: main.c 13953 2013-02-04 16:15:48Z jordan $
3 *
4 * Copyright (c) Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <locale.h>
26#include <signal.h>
27#include <string.h>
28#include <stdio.h>
29#include <stdlib.h> /* exit () */
30#include <time.h>
31
32#include <glib/gi18n.h>
33#include <glib/gstdio.h>
34#include <gio/gio.h>
35#include <gtk/gtk.h>
36
37#include <libtransmission/transmission.h>
38#include <libtransmission/rpcimpl.h>
39#include <libtransmission/utils.h>
40#include <libtransmission/version.h>
41
42#include "actions.h"
43#include "conf.h"
44#include "details.h"
45#include "dialogs.h"
46#include "hig.h"
47#include "makemeta-ui.h"
48#include "msgwin.h"
49#include "notify.h"
50#include "open-dialog.h"
51#include "relocate.h"
52#include "stats.h"
53#include "tr-core.h"
54#include "tr-icon.h"
55#include "tr-prefs.h"
56#include "tr-window.h"
57#include "util.h"
58
59#define MY_CONFIG_NAME "transmission"
60#define MY_READABLE_NAME "transmission-gtk"
61
62#define TR_RESOURCE_PATH "/com/transmissionbt/transmission/"
63
64#define SHOW_LICENSE
65static const char * LICENSE =
66"The OS X client, CLI client, and parts of libtransmission are licensed under the terms of the MIT license.\n\n"
67"The Transmission daemon, GTK+ client, Qt client, Web client, and most of libtransmission are licensed under the terms of the GNU GPL version 2, with two special exceptions:\n\n"
68"1. The MIT-licensed portions of Transmission listed above are exempt from GPLv2 clause 2 (b) and may retain their MIT license.\n\n"
69"2. Permission is granted to link the code in this release with the OpenSSL project's 'OpenSSL' library and to distribute the linked executables. Works derived from Transmission may, at their authors' discretion, keep or delete this exception.";
70
71struct cbdata
72{
73  char * config_dir;
74  gboolean start_paused;
75  gboolean is_iconified;
76  gboolean is_closing;
77
78  guint activation_count;
79  guint timer;
80  guint update_model_soon_tag;
81  guint refresh_actions_tag;
82  gpointer icon;
83  GtkWindow * wind;
84  TrCore * core;
85  GtkWidget * msgwin;
86  GtkWidget * prefs;
87  GSList * error_list;
88  GSList * duplicates_list;
89  GSList * details;
90  GtkTreeSelection * sel;
91  gpointer quit_dialog;
92};
93
94static void
95gtr_window_present (GtkWindow * window)
96{
97  gtk_window_present_with_time (window, gtk_get_current_event_time ());
98}
99
100/***
101****
102****  DETAILS DIALOGS MANAGEMENT
103****
104***/
105
106static int
107compare_integers (gconstpointer a, gconstpointer b)
108{
109  return GPOINTER_TO_INT (a) - GPOINTER_TO_INT (b);
110}
111
112static char*
113get_details_dialog_key (GSList * id_list)
114{
115  GSList * l;
116  GSList * tmp = g_slist_sort (g_slist_copy (id_list), compare_integers);
117  GString * gstr = g_string_new (NULL);
118
119  for (l=tmp; l!=NULL; l=l->next)
120    g_string_append_printf (gstr, "%d ", GPOINTER_TO_INT (l->data));
121
122  g_slist_free (tmp);
123  return g_string_free (gstr, FALSE);
124}
125
126static void
127get_selected_torrent_ids_foreach (GtkTreeModel  * model,
128                                  GtkTreePath   * p UNUSED,
129                                  GtkTreeIter   * iter,
130                                  gpointer        gdata)
131{
132  int id;
133  GSList ** ids = gdata;
134  gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1);
135  *ids = g_slist_append (*ids, GINT_TO_POINTER (id));
136}
137static GSList*
138get_selected_torrent_ids (struct cbdata * data)
139{
140  GSList * ids = NULL;
141  gtk_tree_selection_selected_foreach (data->sel,
142                                       get_selected_torrent_ids_foreach,
143                                       &ids);
144  return ids;
145}
146
147static void
148on_details_dialog_closed (gpointer gdata, GObject * dead)
149{
150  struct cbdata * data = gdata;
151
152  data->details = g_slist_remove (data->details, dead);
153}
154
155static void
156show_details_dialog_for_selected_torrents (struct cbdata * data)
157{
158  GtkWidget * dialog = NULL;
159  GSList * l;
160  GSList * ids = get_selected_torrent_ids (data);
161  char * key = get_details_dialog_key (ids);
162
163  for (l=data->details; dialog==NULL && l!=NULL; l=l->next)
164    if (!strcmp (key, g_object_get_data (l->data, "key")))
165      dialog = l->data;
166
167  if (dialog == NULL)
168    {
169      dialog = gtr_torrent_details_dialog_new (GTK_WINDOW (data->wind), data->core);
170      gtr_torrent_details_dialog_set_torrents (dialog, ids);
171      g_object_set_data_full (G_OBJECT (dialog), "key", g_strdup (key), g_free);
172      g_object_weak_ref (G_OBJECT (dialog), on_details_dialog_closed, data);
173      data->details = g_slist_append (data->details, dialog);
174      gtk_widget_show (dialog);
175    }
176
177  gtr_window_present (GTK_WINDOW (dialog));
178  g_free (key);
179  g_slist_free (ids);
180}
181
182/****
183*****
184*****  ON SELECTION CHANGED
185*****
186****/
187
188struct counts_data
189{
190  int total_count;
191  int queued_count;
192  int stopped_count;
193};
194
195static void
196get_selected_torrent_counts_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED,
197                                     GtkTreeIter * iter, gpointer user_data)
198{
199  int activity = 0;
200  struct counts_data * counts = user_data;
201
202  ++counts->total_count;
203
204  gtk_tree_model_get (model, iter, MC_ACTIVITY, &activity, -1);
205
206  if ((activity == TR_STATUS_DOWNLOAD_WAIT) || (activity == TR_STATUS_SEED_WAIT))
207    ++counts->queued_count;
208
209  if (activity == TR_STATUS_STOPPED)
210    ++counts->stopped_count;
211}
212
213static void
214get_selected_torrent_counts (struct cbdata * data, struct counts_data * counts)
215{
216  counts->total_count = 0;
217  counts->queued_count = 0;
218  counts->stopped_count = 0;
219
220  gtk_tree_selection_selected_foreach (data->sel, get_selected_torrent_counts_foreach, counts);
221}
222
223static void
224count_updatable_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED,
225                         GtkTreeIter * iter, gpointer accumulated_status)
226{
227  tr_torrent * tor;
228  gtk_tree_model_get (model, iter, MC_TORRENT, &tor, -1);
229  *(int*)accumulated_status |= tr_torrentCanManualUpdate (tor);
230}
231
232static gboolean
233refresh_actions (gpointer gdata)
234{
235  int canUpdate;
236  struct counts_data sel_counts;
237  struct cbdata * data = gdata;
238  const size_t total = gtr_core_get_torrent_count (data->core);
239  const size_t active = gtr_core_get_active_torrent_count (data->core);
240  const int torrent_count = gtk_tree_model_iter_n_children (gtr_core_model (data->core), NULL);
241  bool has_selection;
242
243  get_selected_torrent_counts (data, &sel_counts);
244  has_selection = sel_counts.total_count > 0;
245
246  gtr_action_set_sensitive ("select-all", torrent_count != 0);
247  gtr_action_set_sensitive ("deselect-all", torrent_count != 0);
248  gtr_action_set_sensitive ("pause-all-torrents", active != 0);
249  gtr_action_set_sensitive ("start-all-torrents", active != total);
250
251  gtr_action_set_sensitive ("torrent-stop", (sel_counts.stopped_count < sel_counts.total_count));
252  gtr_action_set_sensitive ("torrent-start", (sel_counts.stopped_count) > 0);
253  gtr_action_set_sensitive ("torrent-start-now", (sel_counts.stopped_count + sel_counts.queued_count) > 0);
254  gtr_action_set_sensitive ("torrent-verify",          has_selection);
255  gtr_action_set_sensitive ("remove-torrent",          has_selection);
256  gtr_action_set_sensitive ("delete-torrent",          has_selection);
257  gtr_action_set_sensitive ("relocate-torrent",        has_selection);
258  gtr_action_set_sensitive ("queue-move-top",          has_selection);
259  gtr_action_set_sensitive ("queue-move-up",           has_selection);
260  gtr_action_set_sensitive ("queue-move-down",         has_selection);
261  gtr_action_set_sensitive ("queue-move-bottom",       has_selection);
262  gtr_action_set_sensitive ("show-torrent-properties", has_selection);
263  gtr_action_set_sensitive ("open-torrent-folder", sel_counts.total_count == 1);
264  gtr_action_set_sensitive ("copy-magnet-link-to-clipboard", sel_counts.total_count == 1);
265
266  canUpdate = 0;
267  gtk_tree_selection_selected_foreach (data->sel, count_updatable_foreach, &canUpdate);
268  gtr_action_set_sensitive ("torrent-reannounce", canUpdate != 0);
269
270  data->refresh_actions_tag = 0;
271  return FALSE;
272}
273
274static void
275refresh_actions_soon (gpointer gdata)
276{
277  struct cbdata * data = gdata;
278
279  if (!data->is_closing && !data->refresh_actions_tag)
280    data->refresh_actions_tag = gdk_threads_add_idle (refresh_actions, data);
281}
282
283static void
284on_selection_changed (GtkTreeSelection * s UNUSED, gpointer gdata)
285{
286  refresh_actions_soon (gdata);
287}
288
289/***
290****
291***/
292
293static void
294register_magnet_link_handler (void)
295{
296  GAppInfo * app_info = g_app_info_get_default_for_uri_scheme ("magnet");
297  if (app_info == NULL)
298    {
299      /* there's no default magnet handler, so register ourselves for the job... */
300      GError * error = NULL;
301      app_info = g_app_info_create_from_commandline ("transmission-gtk", "transmission-gtk", G_APP_INFO_CREATE_SUPPORTS_URIS, NULL);
302      g_app_info_set_as_default_for_type (app_info, "x-scheme-handler/magnet", &error);
303      if (error != NULL)
304        {
305          g_warning (_("Error registering Transmission as x-scheme-handler/magnet handler: %s"), error->message);
306          g_clear_error (&error);
307        }
308    }
309}
310
311static void
312on_main_window_size_allocated (GtkWidget      * gtk_window,
313                               GtkAllocation  * alloc UNUSED,
314                               gpointer         gdata UNUSED)
315{
316  GdkWindow * gdk_window = gtk_widget_get_window (gtk_window);
317  const gboolean isMaximized = (gdk_window != NULL)
318                            && (gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_MAXIMIZED);
319
320  gtr_pref_int_set (PREF_KEY_MAIN_WINDOW_IS_MAXIMIZED, isMaximized);
321
322  if (!isMaximized)
323    {
324      int x, y, w, h;
325      gtk_window_get_position (GTK_WINDOW (gtk_window), &x, &y);
326      gtk_window_get_size (GTK_WINDOW (gtk_window), &w, &h);
327      gtr_pref_int_set (PREF_KEY_MAIN_WINDOW_X, x);
328      gtr_pref_int_set (PREF_KEY_MAIN_WINDOW_Y, y);
329      gtr_pref_int_set (PREF_KEY_MAIN_WINDOW_WIDTH, w);
330      gtr_pref_int_set (PREF_KEY_MAIN_WINDOW_HEIGHT, h);
331    }
332}
333
334/***
335**** listen to changes that come from RPC
336***/
337
338struct rpc_idle_data
339{
340  TrCore * core;
341  int id;
342  gboolean delete_files;
343};
344
345static gboolean
346rpc_torrent_remove_idle (gpointer gdata)
347{
348  struct rpc_idle_data * data = gdata;
349
350  gtr_core_remove_torrent (data->core, data->id, data->delete_files);
351
352  g_free (data);
353  return FALSE; /* tell g_idle not to call this func twice */
354}
355
356static gboolean
357rpc_torrent_add_idle (gpointer gdata)
358{
359  tr_torrent * tor;
360  struct rpc_idle_data * data = gdata;
361
362  if ((tor = gtr_core_find_torrent (data->core, data->id)))
363    gtr_core_add_torrent (data->core, tor, TRUE);
364
365  g_free (data);
366  return FALSE; /* tell g_idle not to call this func twice */
367}
368
369static tr_rpc_callback_status
370on_rpc_changed (tr_session            * session,
371                tr_rpc_callback_type    type,
372                struct tr_torrent     * tor,
373                void                  * gdata)
374{
375  tr_rpc_callback_status status = TR_RPC_OK;
376  struct cbdata * cbdata = gdata;
377  gdk_threads_enter ();
378
379  switch (type)
380    {
381      case TR_RPC_SESSION_CLOSE:
382        gtr_action_activate ("quit");
383        break;
384
385      case TR_RPC_TORRENT_ADDED: {
386        struct rpc_idle_data * data = g_new0 (struct rpc_idle_data, 1);
387        data->id = tr_torrentId (tor);
388        data->core = cbdata->core;
389        gdk_threads_add_idle (rpc_torrent_add_idle, data);
390        break;
391      }
392
393      case TR_RPC_TORRENT_REMOVING:
394      case TR_RPC_TORRENT_TRASHING: {
395        struct rpc_idle_data * data = g_new0 (struct rpc_idle_data, 1);
396        data->id = tr_torrentId (tor);
397        data->core = cbdata->core;
398        data->delete_files = type == TR_RPC_TORRENT_TRASHING;
399        gdk_threads_add_idle (rpc_torrent_remove_idle, data);
400        status = TR_RPC_NOREMOVE;
401        break;
402      }
403
404      case TR_RPC_SESSION_CHANGED: {
405        int i;
406        tr_benc tmp;
407        tr_benc * newval;
408        tr_benc * oldvals = gtr_pref_get_all ();
409        const char * key;
410        GSList * l;
411        GSList * changed_keys = NULL;
412        tr_bencInitDict (&tmp, 100);
413        tr_sessionGetSettings (session, &tmp);
414        for (i=0; tr_bencDictChild (&tmp, i, &key, &newval); ++i)
415          {
416            bool changed;
417            tr_benc * oldval = tr_bencDictFind (oldvals, key);
418            if (!oldval)
419              {
420                changed = true;
421              }
422            else
423              {
424                char * a = tr_bencToStr (oldval, TR_FMT_BENC, NULL);
425                char * b = tr_bencToStr (newval, TR_FMT_BENC, NULL);
426                changed = strcmp (a, b) != 0;
427                tr_free (b);
428                tr_free (a);
429              }
430
431            if (changed)
432              changed_keys = g_slist_append (changed_keys, (gpointer)key);
433          }
434        tr_sessionGetSettings (session, oldvals);
435
436        for (l=changed_keys; l!=NULL; l=l->next)
437          gtr_core_pref_changed (cbdata->core, l->data);
438
439        g_slist_free (changed_keys);
440        tr_bencFree (&tmp);
441        break;
442      }
443
444      case TR_RPC_TORRENT_CHANGED:
445      case TR_RPC_TORRENT_MOVED:
446      case TR_RPC_TORRENT_STARTED:
447      case TR_RPC_TORRENT_STOPPED:
448      case TR_RPC_SESSION_QUEUE_POSITIONS_CHANGED:
449        /* nothing interesting to do here */
450        break;
451    }
452
453  gdk_threads_leave ();
454  return status;
455}
456
457/***
458****  signal handling
459***/
460
461static sig_atomic_t global_sigcount = 0;
462static struct cbdata * sighandler_cbdata = NULL;
463
464static void
465signal_handler (int sig)
466{
467  if (++global_sigcount > 1)
468    {
469      signal (sig, SIG_DFL);
470      raise (sig);
471    }
472  else if ((sig == SIGINT) || (sig == SIGTERM))
473    {
474      g_message (_("Got signal %d; trying to shut down cleanly. Do it again if it gets stuck."), sig);
475      gtr_actions_handler ("quit", sighandler_cbdata);
476    }
477}
478
479/****
480*****
481*****
482****/
483
484static void app_setup (GtkWindow * wind, struct cbdata  * cbdata);
485
486static void
487on_startup (GApplication * application, gpointer user_data)
488{
489  GError * error;
490  const char * str;
491  GtkWindow * win;
492  GtkUIManager * ui_manager;
493  tr_session * session;
494  struct cbdata * cbdata = user_data;
495
496  signal (SIGINT, signal_handler);
497  signal (SIGTERM, signal_handler);
498
499  sighandler_cbdata = cbdata;
500
501  /* ensure the directories are created */
502  if ((str = gtr_pref_string_get (TR_PREFS_KEY_DOWNLOAD_DIR)))
503    g_mkdir_with_parents (str, 0777);
504  if ((str = gtr_pref_string_get (TR_PREFS_KEY_INCOMPLETE_DIR)))
505    g_mkdir_with_parents (str, 0777);
506
507  /* initialize the libtransmission session */
508  session = tr_sessionInit ("gtk", cbdata->config_dir, TRUE, gtr_pref_get_all ());
509
510  gtr_pref_flag_set (TR_PREFS_KEY_ALT_SPEED_ENABLED, tr_sessionUsesAltSpeed (session));
511  gtr_pref_int_set (TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort (session));
512  cbdata->core = gtr_core_new (session);
513
514  /* init the ui manager */
515  error = NULL;
516  ui_manager = gtk_ui_manager_new ();
517  gtr_actions_init (ui_manager, cbdata);
518  gtk_ui_manager_add_ui_from_resource (ui_manager, TR_RESOURCE_PATH "transmission-ui.xml", &error);
519  g_assert_no_error (error);
520  gtk_ui_manager_ensure_update (ui_manager);
521
522  /* create main window now to be a parent to any error dialogs */
523  win = GTK_WINDOW (gtr_window_new (GTK_APPLICATION (application), ui_manager, cbdata->core));
524  g_signal_connect (win, "size-allocate", G_CALLBACK (on_main_window_size_allocated), cbdata);
525  g_application_hold (application);
526  g_object_weak_ref (G_OBJECT (win), (GWeakNotify)g_application_release, application);
527  app_setup (win, cbdata);
528  tr_sessionSetRPCCallback (session, on_rpc_changed, cbdata);
529
530  /* check & see if it's time to update the blocklist */
531  if (gtr_pref_flag_get (TR_PREFS_KEY_BLOCKLIST_ENABLED))
532    {
533      if (gtr_pref_flag_get (PREF_KEY_BLOCKLIST_UPDATES_ENABLED))
534        {
535          const int64_t last_time = gtr_pref_int_get ("blocklist-date");
536          const int SECONDS_IN_A_WEEK = 7 * 24 * 60 * 60;
537          const time_t now = time (NULL);
538          if (last_time + SECONDS_IN_A_WEEK < now)
539            gtr_core_blocklist_update (cbdata->core);
540        }
541    }
542
543  /* if there's no magnet link handler registered, register us */
544  register_magnet_link_handler ();
545}
546
547static void
548on_activate (GApplication * app UNUSED, struct cbdata * cbdata)
549{
550  cbdata->activation_count++;
551
552  /* GApplication emits an 'activate' signal when bootstrapping the primary.
553   * Ordinarily we handle that by presenting the main window, but if the user
554   * user started Transmission minimized, ignore that initial signal... */
555  if (cbdata->is_iconified && (cbdata->activation_count == 1))
556    return;
557
558  gtr_action_activate ("present-main-window");
559}
560
561static void
562open_files (GSList * files, gpointer gdata)
563{
564  struct cbdata * cbdata = gdata;
565  const gboolean do_start = gtr_pref_flag_get (TR_PREFS_KEY_START) && !cbdata->start_paused;
566  const gboolean do_prompt = gtr_pref_flag_get (PREF_KEY_OPTIONS_PROMPT);
567  const gboolean do_notify = TRUE;
568
569  gtr_core_add_files (cbdata->core, files, do_start, do_prompt, do_notify);
570}
571
572static void
573on_open (GApplication  * application UNUSED,
574         GFile        ** f,
575         gint            file_count,
576         gchar         * hint UNUSED,
577         gpointer        gdata)
578{
579  int i;
580  GSList * files = NULL;
581
582  for (i=0; i<file_count; i++)
583    files = g_slist_prepend (files, f[i]);
584
585  open_files (files, gdata);
586
587  g_slist_free (files);
588}
589
590/***
591****
592***/
593
594int
595main (int argc, char ** argv)
596{
597  int ret;
598  struct stat sb;
599  char * application_id;
600  GtkApplication * app;
601  GOptionContext * option_context;
602  bool show_version = false;
603  GError * error = NULL;
604  struct cbdata cbdata;
605
606  GOptionEntry option_entries[] = {
607    { "config-dir", 'g', 0, G_OPTION_ARG_FILENAME, &cbdata.config_dir, _("Where to look for configuration files"), NULL },
608    { "paused",     'p', 0, G_OPTION_ARG_NONE, &cbdata.start_paused, _("Start with all torrents paused"), NULL },
609    { "minimized",  'm', 0, G_OPTION_ARG_NONE, &cbdata.is_iconified, _("Start minimized in notification area"), NULL },
610    { "version",    'v', 0, G_OPTION_ARG_NONE, &show_version, _("Show version number and exit"), NULL },
611    { NULL, 0,   0, 0, NULL, NULL, NULL }
612  };
613
614  /* default settings */
615  memset (&cbdata, 0, sizeof (struct cbdata));
616  cbdata.config_dir = (char*) tr_getDefaultConfigDir (MY_CONFIG_NAME);
617
618  /* init i18n */
619  setlocale (LC_ALL, "");
620  bindtextdomain (MY_READABLE_NAME, TRANSMISSIONLOCALEDIR);
621  bind_textdomain_codeset (MY_READABLE_NAME, "UTF-8");
622  textdomain (MY_READABLE_NAME);
623
624  /* init glib/gtk */
625  g_type_init ();
626  gtk_init (&argc, &argv);
627  g_set_application_name (_("Transmission"));
628  gtk_window_set_default_icon_name (MY_CONFIG_NAME);
629
630  /* parse the command line */
631  option_context = g_option_context_new (_("[torrent files or urls]"));
632  g_option_context_add_main_entries (option_context, option_entries, GETTEXT_PACKAGE);
633  g_option_context_set_translation_domain (option_context, GETTEXT_PACKAGE);
634  if (!g_option_context_parse (option_context, &argc, &argv, &error))
635    {
636      g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"), error->message, argv[0]);
637      g_error_free (error);
638      g_option_context_free (option_context);
639      return 1;
640    }
641  g_option_context_free (option_context);
642
643  /* handle the trivial "version" option */
644  if (show_version)
645    {
646      fprintf (stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING);
647      return 0;
648    }
649
650  /* init the unit formatters */
651  tr_formatter_mem_init (mem_K, _ (mem_K_str), _ (mem_M_str), _ (mem_G_str), _ (mem_T_str));
652  tr_formatter_size_init (disk_K, _ (disk_K_str), _ (disk_M_str), _ (disk_G_str), _ (disk_T_str));
653  tr_formatter_speed_init (speed_K, _ (speed_K_str), _ (speed_M_str), _ (speed_G_str), _ (speed_T_str));
654
655  /* set up the config dir */
656  gtr_pref_init (cbdata.config_dir);
657  g_mkdir_with_parents (cbdata.config_dir, 0755);
658
659  /* init notifications */
660  gtr_notify_init ();
661
662  /* init the application for the specified config dir */
663  stat (cbdata.config_dir, &sb);
664  application_id = g_strdup_printf ("com.transmissionbt.transmission_%lu_%lu", (unsigned long)sb.st_dev, (unsigned long)sb.st_ino);
665  app = gtk_application_new (application_id, G_APPLICATION_HANDLES_OPEN);
666  g_signal_connect (app, "open", G_CALLBACK (on_open), &cbdata);
667  g_signal_connect (app, "startup", G_CALLBACK (on_startup), &cbdata);
668  g_signal_connect (app, "activate", G_CALLBACK (on_activate), &cbdata);
669  ret = g_application_run (G_APPLICATION (app), argc, argv);
670  g_object_unref (app);
671  g_free (application_id);
672  return ret;
673}
674
675static void
676on_core_busy (TrCore * core UNUSED, gboolean busy, struct cbdata * c)
677{
678  gtr_window_set_busy (c->wind, busy);
679}
680
681static void on_core_error (TrCore *, guint, const char *, struct cbdata *);
682static void on_add_torrent (TrCore *, tr_ctor *, gpointer);
683static void on_prefs_changed (TrCore * core, const char * key, gpointer);
684static void main_window_setup (struct cbdata * cbdata, GtkWindow * wind);
685static gboolean update_model_loop (gpointer gdata);
686static gboolean update_model_once (gpointer gdata);
687
688static void
689app_setup (GtkWindow * wind, struct cbdata * cbdata)
690{
691  if (cbdata->is_iconified)
692    gtr_pref_flag_set (PREF_KEY_SHOW_TRAY_ICON, TRUE);
693
694  gtr_actions_set_core (cbdata->core);
695
696  /* set up core handlers */
697  g_signal_connect (cbdata->core, "busy", G_CALLBACK (on_core_busy), cbdata);
698  g_signal_connect (cbdata->core, "add-error", G_CALLBACK (on_core_error), cbdata);
699  g_signal_connect (cbdata->core, "add-prompt", G_CALLBACK (on_add_torrent), cbdata);
700  g_signal_connect (cbdata->core, "prefs-changed", G_CALLBACK (on_prefs_changed), cbdata);
701
702  /* add torrents from command-line and saved state */
703  gtr_core_load (cbdata->core, cbdata->start_paused);
704  gtr_core_torrents_added (cbdata->core);
705
706  /* set up main window */
707  main_window_setup (cbdata, wind);
708
709  /* set up the icon */
710  on_prefs_changed (cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata);
711
712  /* start model update timer */
713  cbdata->timer = gdk_threads_add_timeout_seconds (MAIN_WINDOW_REFRESH_INTERVAL_SECONDS, update_model_loop, cbdata);
714  update_model_once (cbdata);
715
716  /* either show the window or iconify it */
717  if (!cbdata->is_iconified)
718    {
719      gtk_widget_show (GTK_WIDGET (wind));
720    }
721  else
722    {
723      gtk_window_set_skip_taskbar_hint (cbdata->wind,
724                                        cbdata->icon != NULL);
725      cbdata->is_iconified = FALSE; // ensure that the next toggle iconifies
726      gtr_action_set_toggled ("toggle-main-window", FALSE);
727    }
728
729  if (!gtr_pref_flag_get (PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT))
730    {
731      GtkWidget * w = gtk_message_dialog_new (GTK_WINDOW (wind),
732                                              GTK_DIALOG_DESTROY_WITH_PARENT,
733                                              GTK_MESSAGE_INFO,
734                                              GTK_BUTTONS_NONE,
735                                              "%s",
736        _("Transmission is a file-sharing program. When you run a torrent, its data will be made available to others by means of upload. You and you alone are fully responsible for exercising proper judgement and abiding by your local laws."));
737      gtk_dialog_add_button (GTK_DIALOG (w), GTK_STOCK_QUIT, GTK_RESPONSE_REJECT);
738      gtk_dialog_add_button (GTK_DIALOG (w), _("I _Accept"), GTK_RESPONSE_ACCEPT);
739      gtk_dialog_set_default_response (GTK_DIALOG (w), GTK_RESPONSE_ACCEPT);
740      switch (gtk_dialog_run (GTK_DIALOG (w)))
741        {
742          case GTK_RESPONSE_ACCEPT:
743            /* only show it once */
744            gtr_pref_flag_set (PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT, TRUE);
745            gtk_widget_destroy (w);
746            break;
747
748          default:
749            exit (0);
750        }
751    }
752}
753
754static void
755presentMainWindow (struct cbdata * cbdata)
756{
757  GtkWindow * window = cbdata->wind;
758
759  if (cbdata->is_iconified)
760    {
761      cbdata->is_iconified = false;
762
763      gtk_window_set_skip_taskbar_hint (window, FALSE);
764    }
765
766  if (!gtk_widget_get_visible (GTK_WIDGET (window)))
767    {
768      gtk_window_resize (window, gtr_pref_int_get (PREF_KEY_MAIN_WINDOW_WIDTH),
769                                 gtr_pref_int_get (PREF_KEY_MAIN_WINDOW_HEIGHT));
770      gtk_window_move (window, gtr_pref_int_get (PREF_KEY_MAIN_WINDOW_X),
771                               gtr_pref_int_get (PREF_KEY_MAIN_WINDOW_Y));
772      gtr_widget_set_visible (GTK_WIDGET (window), TRUE);
773    }
774
775  gtr_window_present (window);
776  gdk_window_raise (gtk_widget_get_window (GTK_WIDGET(window)));
777}
778
779static void
780hideMainWindow (struct cbdata * cbdata)
781{
782  GtkWindow * window = cbdata->wind;
783  gtk_window_set_skip_taskbar_hint (window, TRUE);
784  gtr_widget_set_visible (GTK_WIDGET (window), FALSE);
785  cbdata->is_iconified = true;
786}
787
788static void
789toggleMainWindow (struct cbdata * cbdata)
790{
791  if (cbdata->is_iconified)
792    presentMainWindow (cbdata);
793  else
794    hideMainWindow (cbdata);
795}
796
797static void on_app_exit (gpointer vdata);
798
799static gboolean
800winclose (GtkWidget * w    UNUSED,
801          GdkEvent * event UNUSED,
802          gpointer         gdata)
803{
804  struct cbdata * cbdata = gdata;
805
806  if (cbdata->icon != NULL)
807    gtr_action_activate ("toggle-main-window");
808  else
809    on_app_exit (cbdata);
810
811  return TRUE; /* don't propagate event further */
812}
813
814static void
815rowChangedCB (GtkTreeModel  * model UNUSED,
816              GtkTreePath   * path,
817              GtkTreeIter   * iter  UNUSED,
818              gpointer        gdata)
819{
820  struct cbdata * data = gdata;
821
822  if (gtk_tree_selection_path_is_selected (data->sel, path))
823    refresh_actions_soon (data);
824}
825
826static void
827on_drag_data_received (GtkWidget         * widget          UNUSED,
828                       GdkDragContext    * drag_context,
829                       gint                x               UNUSED,
830                       gint                y               UNUSED,
831                       GtkSelectionData  * selection_data,
832                       guint               info            UNUSED,
833                       guint               time_,
834                       gpointer            gdata)
835{
836  guint i;
837  char ** uris = gtk_selection_data_get_uris (selection_data);
838  const guint file_count = g_strv_length (uris);
839  GSList * files = NULL;
840
841  for (i=0; i<file_count; ++i)
842    files = g_slist_prepend (files, g_file_new_for_uri (uris[i]));
843
844  open_files (files, gdata);
845
846  /* cleanup */
847  g_slist_foreach (files, (GFunc)g_object_unref, NULL);
848  g_slist_free (files);
849  g_strfreev (uris);
850
851  gtk_drag_finish (drag_context, true, FALSE, time_);
852}
853
854static void
855main_window_setup (struct cbdata * cbdata, GtkWindow * wind)
856{
857  GtkWidget * w;
858  GtkTreeModel * model;
859  GtkTreeSelection * sel;
860
861  g_assert (NULL == cbdata->wind);
862  cbdata->wind = wind;
863  cbdata->sel = sel = GTK_TREE_SELECTION (gtr_window_get_selection (cbdata->wind));
864
865  g_signal_connect (sel, "changed", G_CALLBACK (on_selection_changed), cbdata);
866  on_selection_changed (sel, cbdata);
867  model = gtr_core_model (cbdata->core);
868  g_signal_connect (model, "row-changed", G_CALLBACK (rowChangedCB), cbdata);
869  g_signal_connect (wind, "delete-event", G_CALLBACK (winclose), cbdata);
870  refresh_actions (cbdata);
871
872  /* register to handle URIs that get dragged onto our main window */
873  w = GTK_WIDGET (wind);
874  gtk_drag_dest_set (w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
875  gtk_drag_dest_add_uri_targets (w);
876  g_signal_connect (w, "drag-data-received", G_CALLBACK (on_drag_data_received), cbdata);
877}
878
879static gboolean
880on_session_closed (gpointer gdata)
881{
882  GSList * tmp;
883  struct cbdata * cbdata = gdata;
884
885  tmp = g_slist_copy (cbdata->details);
886  g_slist_foreach (tmp, (GFunc)gtk_widget_destroy, NULL);
887  g_slist_free (tmp);
888
889  if (cbdata->prefs)
890    gtk_widget_destroy (GTK_WIDGET (cbdata->prefs));
891  if (cbdata->wind)
892    gtk_widget_destroy (GTK_WIDGET (cbdata->wind));
893  g_object_unref (cbdata->core);
894  if (cbdata->icon)
895    g_object_unref (cbdata->icon);
896  g_slist_foreach (cbdata->error_list, (GFunc)g_free, NULL);
897  g_slist_free (cbdata->error_list);
898  g_slist_foreach (cbdata->duplicates_list, (GFunc)g_free, NULL);
899  g_slist_free (cbdata->duplicates_list);
900
901  return FALSE;
902}
903
904static gpointer
905session_close_threadfunc (gpointer gdata)
906{
907  /* since tr_sessionClose () is a blocking function,
908   * call it from another thread... when it's done,
909   * punt the GUI teardown back to the GTK+ thread */
910  struct cbdata * cbdata = gdata;
911  gdk_threads_enter ();
912  gtr_core_close (cbdata->core);
913  gdk_threads_add_idle (on_session_closed, gdata);
914  gdk_threads_leave ();
915  return NULL;
916}
917
918static void
919exit_now_cb (GtkWidget *w UNUSED, gpointer data UNUSED)
920{
921  exit (0);
922}
923
924static void
925on_app_exit (gpointer vdata)
926{
927  GtkWidget *r, *p, *b, *w, *c;
928  struct cbdata *cbdata = vdata;
929
930  cbdata->is_closing = true;
931
932  /* stop the update timer */
933  if (cbdata->timer)
934    {
935      g_source_remove (cbdata->timer);
936      cbdata->timer = 0;
937    }
938
939  /* stop the refresh-actions timer */
940  if (cbdata->refresh_actions_tag)
941    {
942      g_source_remove (cbdata->refresh_actions_tag);
943      cbdata->refresh_actions_tag = 0;
944    }
945
946  c = GTK_WIDGET (cbdata->wind);
947  gtk_container_remove (GTK_CONTAINER (c), gtk_bin_get_child (GTK_BIN (c)));
948
949  r = gtk_alignment_new (0.5, 0.5, 0.01, 0.01);
950  gtk_container_add (GTK_CONTAINER (c), r);
951
952  p = gtk_grid_new ();
953  gtk_grid_set_column_spacing (GTK_GRID (p), GUI_PAD_BIG);
954  gtk_container_add (GTK_CONTAINER (r), p);
955
956  w = gtk_image_new_from_stock (GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG);
957  gtk_grid_attach (GTK_GRID (p), w, 0, 0, 1, 2);
958
959  w = gtk_label_new (NULL);
960  gtk_label_set_markup (GTK_LABEL (w), _("<b>Closing Connections</b>"));
961  gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
962  gtk_grid_attach (GTK_GRID (p), w, 1, 0, 1, 1);
963
964  w = gtk_label_new (_("Sending upload/download totals to tracker
"));
965  gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
966  gtk_grid_attach (GTK_GRID (p), w, 1, 1, 1, 1);
967
968  b = gtk_alignment_new (0.0, 1.0, 0.01, 0.01);
969  w = gtk_button_new_with_mnemonic (_("_Quit Now"));
970  g_signal_connect (w, "clicked", G_CALLBACK (exit_now_cb), NULL);
971  gtk_container_add (GTK_CONTAINER (b), w);
972  gtk_grid_attach (GTK_GRID (p), b, 1, 2, 1, 1);
973
974  gtk_widget_show_all (r);
975  gtk_widget_grab_focus (w);
976
977  /* clear the UI */
978  gtr_core_clear (cbdata->core);
979
980  /* ensure the window is in its previous position & size.
981   * this seems to be necessary because changing the main window's
982   * child seems to unset the size */
983  gtk_window_resize (cbdata->wind, gtr_pref_int_get (PREF_KEY_MAIN_WINDOW_WIDTH),
984                                   gtr_pref_int_get (PREF_KEY_MAIN_WINDOW_HEIGHT));
985  gtk_window_move (cbdata->wind, gtr_pref_int_get (PREF_KEY_MAIN_WINDOW_X),
986                                 gtr_pref_int_get (PREF_KEY_MAIN_WINDOW_Y));
987
988  /* shut down libT */
989  g_thread_new ("shutdown-thread", session_close_threadfunc, vdata);
990}
991
992static void
993show_torrent_errors (GtkWindow * window, const char * primary, GSList ** files)
994{
995  GSList * l;
996  GtkWidget * w;
997  GString * s = g_string_new (NULL);
998  const char * leader = g_slist_length (*files) > 1
999                      ? gtr_get_unicode_string (GTR_UNICODE_BULLET)
1000                      : "";
1001
1002  for (l=*files; l!=NULL; l=l->next)
1003    g_string_append_printf (s, "%s %s\n", leader, (const char*)l->data);
1004
1005  w = gtk_message_dialog_new (window,
1006                              GTK_DIALOG_DESTROY_WITH_PARENT,
1007                              GTK_MESSAGE_ERROR,
1008                              GTK_BUTTONS_CLOSE,
1009                              "%s", primary);
1010  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (w),
1011                                            "%s", s->str);
1012  g_signal_connect_swapped (w, "response",
1013                            G_CALLBACK (gtk_widget_destroy), w);
1014  gtk_widget_show (w);
1015  g_string_free (s, TRUE);
1016
1017  g_slist_foreach (*files, (GFunc)g_free, NULL);
1018  g_slist_free (*files);
1019  *files = NULL;
1020}
1021
1022static void
1023flush_torrent_errors (struct cbdata * cbdata)
1024{
1025  if (cbdata->error_list)
1026    show_torrent_errors (cbdata->wind,
1027                         ngettext ("Couldn't add corrupt torrent",
1028                                   "Couldn't add corrupt torrents",
1029                                   g_slist_length (cbdata->error_list)),
1030                         &cbdata->error_list);
1031
1032  if (cbdata->duplicates_list)
1033    show_torrent_errors (cbdata->wind,
1034                         ngettext ("Couldn't add duplicate torrent",
1035                                   "Couldn't add duplicate torrents",
1036                                   g_slist_length (cbdata->duplicates_list)),
1037                         &cbdata->duplicates_list);
1038}
1039
1040static void
1041on_core_error (TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c)
1042{
1043  switch (code)
1044    {
1045      case TR_PARSE_ERR:
1046        c->error_list = g_slist_append (c->error_list, g_path_get_basename (msg));
1047        break;
1048
1049      case TR_PARSE_DUPLICATE:
1050        c->duplicates_list = g_slist_append (c->duplicates_list, g_strdup (msg));
1051        break;
1052
1053      case TR_CORE_ERR_NO_MORE_TORRENTS:
1054        flush_torrent_errors (c);
1055        break;
1056
1057      default:
1058        g_assert_not_reached ();
1059        break;
1060    }
1061}
1062
1063static gboolean
1064on_main_window_focus_in (GtkWidget      * widget UNUSED,
1065                         GdkEventFocus  * event  UNUSED,
1066                         gpointer         gdata)
1067{
1068  struct cbdata * cbdata = gdata;
1069
1070  if (cbdata->wind)
1071    gtk_window_set_urgency_hint (cbdata->wind, FALSE);
1072
1073  return FALSE;
1074}
1075
1076static void
1077on_add_torrent (TrCore * core, tr_ctor * ctor, gpointer gdata)
1078{
1079  struct cbdata * cbdata = gdata;
1080  GtkWidget * w = gtr_torrent_options_dialog_new (cbdata->wind, core, ctor);
1081
1082  g_signal_connect (w, "focus-in-event",
1083                    G_CALLBACK (on_main_window_focus_in),  cbdata);
1084  if (cbdata->wind)
1085    gtk_window_set_urgency_hint (cbdata->wind, TRUE);
1086
1087  gtk_widget_show (w);
1088}
1089
1090static void
1091on_prefs_changed (TrCore * core UNUSED, const char * key, gpointer data)
1092{
1093  struct cbdata * cbdata = data;
1094  tr_session * tr = gtr_core_session (cbdata->core);
1095
1096  if (!strcmp (key, TR_PREFS_KEY_ENCRYPTION))
1097    {
1098      tr_sessionSetEncryption (tr, gtr_pref_int_get (key));
1099    }
1100  else if (!strcmp (key, TR_PREFS_KEY_DOWNLOAD_DIR))
1101    {
1102      tr_sessionSetDownloadDir (tr, gtr_pref_string_get (key));
1103    }
1104  else if (!strcmp (key, TR_PREFS_KEY_MSGLEVEL))
1105    {
1106      tr_setMessageLevel (gtr_pref_int_get (key));
1107    }
1108  else if (!strcmp (key, TR_PREFS_KEY_PEER_PORT))
1109    {
1110      tr_sessionSetPeerPort (tr, gtr_pref_int_get (key));
1111    }
1112  else if (!strcmp (key, TR_PREFS_KEY_BLOCKLIST_ENABLED))
1113    {
1114        tr_blocklistSetEnabled (tr, gtr_pref_flag_get (key));
1115    }
1116  else if (!strcmp (key, TR_PREFS_KEY_BLOCKLIST_URL))
1117    {
1118        tr_blocklistSetURL (tr, gtr_pref_string_get (key));
1119    }
1120  else if (!strcmp (key, PREF_KEY_SHOW_TRAY_ICON))
1121    {
1122      const int show = gtr_pref_flag_get (key);
1123      if (show && !cbdata->icon)
1124        cbdata->icon = gtr_icon_new (cbdata->core);
1125      else if (!show && cbdata->icon)
1126        g_clear_object (&cbdata->icon);
1127    }
1128  else if (!strcmp (key, TR_PREFS_KEY_DSPEED_ENABLED))
1129    {
1130      tr_sessionLimitSpeed (tr, TR_DOWN, gtr_pref_flag_get (key));
1131    }
1132  else if (!strcmp (key, TR_PREFS_KEY_DSPEED_KBps))
1133    {
1134      tr_sessionSetSpeedLimit_KBps (tr, TR_DOWN, gtr_pref_int_get (key));
1135    }
1136  else if (!strcmp (key, TR_PREFS_KEY_USPEED_ENABLED))
1137    {
1138      tr_sessionLimitSpeed (tr, TR_UP, gtr_pref_flag_get (key));
1139    }
1140  else if (!strcmp (key, TR_PREFS_KEY_USPEED_KBps))
1141    {
1142      tr_sessionSetSpeedLimit_KBps (tr, TR_UP, gtr_pref_int_get (key));
1143    }
1144  else if (!strcmp (key, TR_PREFS_KEY_RATIO_ENABLED))
1145    {
1146      tr_sessionSetRatioLimited (tr, gtr_pref_flag_get (key));
1147    }
1148  else if (!strcmp (key, TR_PREFS_KEY_RATIO))
1149    {
1150      tr_sessionSetRatioLimit (tr, gtr_pref_double_get (key));
1151    }
1152  else if (!strcmp (key, TR_PREFS_KEY_IDLE_LIMIT))
1153    {
1154      tr_sessionSetIdleLimit (tr, gtr_pref_int_get (key));
1155    }
1156  else if (!strcmp (key, TR_PREFS_KEY_IDLE_LIMIT_ENABLED))
1157    {
1158      tr_sessionSetIdleLimited (tr, gtr_pref_flag_get (key));
1159    }
1160  else if (!strcmp (key, TR_PREFS_KEY_PORT_FORWARDING))
1161    {
1162      tr_sessionSetPortForwardingEnabled (tr, gtr_pref_flag_get (key));
1163    }
1164  else if (!strcmp (key, TR_PREFS_KEY_PEX_ENABLED))
1165    {
1166      tr_sessionSetPexEnabled (tr, gtr_pref_flag_get (key));
1167    }
1168  else if (!strcmp (key, TR_PREFS_KEY_RENAME_PARTIAL_FILES))
1169    {
1170      tr_sessionSetIncompleteFileNamingEnabled (tr, gtr_pref_flag_get (key));
1171    }
1172  else if (!strcmp (key, TR_PREFS_KEY_DOWNLOAD_QUEUE_SIZE))
1173    {
1174      tr_sessionSetQueueSize (tr, TR_DOWN, gtr_pref_int_get (key));
1175    }
1176  else if (!strcmp (key, TR_PREFS_KEY_QUEUE_STALLED_MINUTES))
1177    {
1178      tr_sessionSetQueueStalledMinutes (tr, gtr_pref_int_get (key));
1179    }
1180  else if (!strcmp (key, TR_PREFS_KEY_DHT_ENABLED))
1181    {
1182      tr_sessionSetDHTEnabled (tr, gtr_pref_flag_get (key));
1183    }
1184  else if (!strcmp (key, TR_PREFS_KEY_UTP_ENABLED))
1185    {
1186      tr_sessionSetUTPEnabled (tr, gtr_pref_flag_get (key));
1187    }
1188  else if (!strcmp (key, TR_PREFS_KEY_LPD_ENABLED))
1189    {
1190      tr_sessionSetLPDEnabled (tr, gtr_pref_flag_get (key));
1191    }
1192  else if (!strcmp (key, TR_PREFS_KEY_RPC_PORT))
1193    {
1194      tr_sessionSetRPCPort (tr, gtr_pref_int_get (key));
1195    }
1196  else if (!strcmp (key, TR_PREFS_KEY_RPC_ENABLED))
1197    {
1198      tr_sessionSetRPCEnabled (tr, gtr_pref_flag_get (key));
1199    }
1200  else if (!strcmp (key, TR_PREFS_KEY_RPC_WHITELIST))
1201    {
1202      tr_sessionSetRPCWhitelist (tr, gtr_pref_string_get (key));
1203    }
1204  else if (!strcmp (key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED))
1205    {
1206      tr_sessionSetRPCWhitelistEnabled (tr, gtr_pref_flag_get (key));
1207    }
1208  else if (!strcmp (key, TR_PREFS_KEY_RPC_USERNAME))
1209    {
1210      tr_sessionSetRPCUsername (tr, gtr_pref_string_get (key));
1211    }
1212  else if (!strcmp (key, TR_PREFS_KEY_RPC_PASSWORD))
1213    {
1214      tr_sessionSetRPCPassword (tr, gtr_pref_string_get (key));
1215    }
1216  else if (!strcmp (key, TR_PREFS_KEY_RPC_AUTH_REQUIRED))
1217    {
1218      tr_sessionSetRPCPasswordEnabled (tr, gtr_pref_flag_get (key));
1219    }
1220  else if (!strcmp (key, TR_PREFS_KEY_ALT_SPEED_UP_KBps))
1221    {
1222      tr_sessionSetAltSpeed_KBps (tr, TR_UP, gtr_pref_int_get (key));
1223    }
1224  else if (!strcmp (key, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps))
1225    {
1226        tr_sessionSetAltSpeed_KBps (tr, TR_DOWN, gtr_pref_int_get (key));
1227    }
1228  else if (!strcmp (key, TR_PREFS_KEY_ALT_SPEED_ENABLED))
1229    {
1230      const gboolean b = gtr_pref_flag_get (key);
1231      tr_sessionUseAltSpeed (tr, b);
1232      gtr_action_set_toggled (key, b);
1233    }
1234  else if (!strcmp (key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN))
1235    {
1236      tr_sessionSetAltSpeedBegin (tr, gtr_pref_int_get (key));
1237    }
1238  else if (!strcmp (key, TR_PREFS_KEY_ALT_SPEED_TIME_END))
1239    {
1240      tr_sessionSetAltSpeedEnd (tr, gtr_pref_int_get (key));
1241    }
1242  else if (!strcmp (key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED))
1243    {
1244      tr_sessionUseAltSpeedTime (tr, gtr_pref_flag_get (key));
1245    }
1246  else if (!strcmp (key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY))
1247    {
1248      tr_sessionSetAltSpeedDay (tr, gtr_pref_int_get (key));
1249    }
1250  else if (!strcmp (key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START))
1251    {
1252      tr_sessionSetPeerPortRandomOnStart (tr, gtr_pref_flag_get (key));
1253    }
1254  else if (!strcmp (key, TR_PREFS_KEY_INCOMPLETE_DIR))
1255    {
1256      tr_sessionSetIncompleteDir (tr, gtr_pref_string_get (key));
1257    }
1258  else if (!strcmp (key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED))
1259    {
1260      tr_sessionSetIncompleteDirEnabled (tr, gtr_pref_flag_get (key));
1261    }
1262  else if (!strcmp (key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED))
1263    {
1264      tr_sessionSetTorrentDoneScriptEnabled (tr, gtr_pref_flag_get (key));
1265    }
1266  else if (!strcmp (key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME))
1267    {
1268      tr_sessionSetTorrentDoneScript (tr, gtr_pref_string_get (key));
1269    }
1270  else if (!strcmp (key, TR_PREFS_KEY_START))
1271    {
1272      tr_sessionSetPaused (tr, !gtr_pref_flag_get (key));
1273    }
1274  else if (!strcmp (key, TR_PREFS_KEY_TRASH_ORIGINAL))
1275    {
1276      tr_sessionSetDeleteSource (tr, gtr_pref_flag_get (key));
1277    }
1278}
1279
1280static gboolean
1281update_model_once (gpointer gdata)
1282{
1283  struct cbdata *data = gdata;
1284
1285  /* update the torrent data in the model */
1286  gtr_core_update (data->core);
1287
1288  /* refresh the main window's statusbar and toolbar buttons */
1289  if (data->wind != NULL)
1290    gtr_window_refresh (data->wind);
1291
1292  /* update the actions */
1293  refresh_actions (data);
1294
1295  /* update the status tray icon */
1296  if (data->icon != NULL)
1297    gtr_icon_refresh (data->icon);
1298
1299  data->update_model_soon_tag = 0;
1300  return FALSE;
1301}
1302
1303static void
1304update_model_soon (gpointer gdata)
1305{
1306  struct cbdata *data = gdata;
1307
1308  if (data->update_model_soon_tag == 0)
1309    data->update_model_soon_tag = gdk_threads_add_idle (update_model_once, data);
1310}
1311
1312static gboolean
1313update_model_loop (gpointer gdata)
1314{
1315  const gboolean done = global_sigcount;
1316
1317  if (!done)
1318    update_model_once (gdata);
1319
1320  return !done;
1321}
1322
1323static void
1324show_about_dialog (GtkWindow * parent)
1325{
1326  const char * uri = "http://www.transmissionbt.com/";
1327  const char * authors[] = { "Jordan Lee (Backend; GTK+)",
1328                             "Mitchell Livingston (Backend; OS X)",
1329                             NULL };
1330
1331  gtk_show_about_dialog (parent,
1332    "authors", authors,
1333    "comments", _("A fast and easy BitTorrent client"),
1334    "copyright", _("Copyright (c) The Transmission Project"),
1335    "logo-icon-name", MY_CONFIG_NAME,
1336    "name", g_get_application_name (),
1337    /* Translators: translate "translator-credits" as your name
1338       to have it appear in the credits in the "About"
1339       dialog */
1340    "translator-credits", _("translator-credits"),
1341    "version", LONG_VERSION_STRING,
1342    "website", uri,
1343    "website-label", uri,
1344#ifdef SHOW_LICENSE
1345    "license", LICENSE,
1346    "wrap-license", TRUE,
1347#endif
1348    NULL);
1349}
1350
1351static void
1352append_id_to_benc_list (GtkTreeModel * m, GtkTreePath * path UNUSED,
1353                        GtkTreeIter * iter, gpointer list)
1354{
1355  tr_torrent * tor = NULL;
1356  gtk_tree_model_get (m, iter, MC_TORRENT, &tor, -1);
1357  tr_bencListAddInt (list, tr_torrentId (tor));
1358}
1359
1360static gboolean
1361call_rpc_for_selected_torrents (struct cbdata * data, const char * method)
1362{
1363  tr_benc top, *args, *ids;
1364  gboolean invoked = FALSE;
1365  GtkTreeSelection * s = data->sel;
1366  tr_session * session = gtr_core_session (data->core);
1367
1368  tr_bencInitDict (&top, 2);
1369  tr_bencDictAddStr (&top, "method", method);
1370  args = tr_bencDictAddDict (&top, "arguments", 1);
1371  ids = tr_bencDictAddList (args, "ids", 0);
1372  gtk_tree_selection_selected_foreach (s, append_id_to_benc_list, ids);
1373
1374  if (tr_bencListSize (ids) != 0)
1375    {
1376      int json_len;
1377      char * json = tr_bencToStr (&top, TR_FMT_JSON_LEAN, &json_len);
1378      tr_rpc_request_exec_json (session, json, json_len, NULL, NULL);
1379      g_free (json);
1380      invoked = TRUE;
1381    }
1382
1383  tr_bencFree (&top);
1384  return invoked;
1385}
1386
1387static void
1388open_folder_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED,
1389                     GtkTreeIter * iter, gpointer core)
1390{
1391  int id;
1392  gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1);
1393  gtr_core_open_folder (core, id);
1394}
1395
1396static gboolean
1397on_message_window_closed (void)
1398{
1399  gtr_action_set_toggled ("toggle-message-log", FALSE);
1400  return FALSE;
1401}
1402
1403static void
1404accumulate_selected_torrents (GtkTreeModel  * model, GtkTreePath   * path UNUSED,
1405                              GtkTreeIter   * iter, gpointer        gdata)
1406{
1407  int id;
1408  GSList ** data = gdata;
1409
1410  gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1);
1411  *data = g_slist_append (*data, GINT_TO_POINTER (id));
1412}
1413
1414static void
1415remove_selected (struct cbdata * data, gboolean delete_files)
1416{
1417  GSList * l = NULL;
1418
1419  gtk_tree_selection_selected_foreach (data->sel, accumulate_selected_torrents, &l);
1420
1421  if (l != NULL)
1422    gtr_confirm_remove (data->wind, data->core, l, delete_files);
1423}
1424
1425static void
1426start_all_torrents (struct cbdata * data)
1427{
1428  tr_session * session = gtr_core_session (data->core);
1429  const char * cmd = "{ \"method\": \"torrent-start\" }";
1430  tr_rpc_request_exec_json (session, cmd, strlen (cmd), NULL, NULL);
1431}
1432
1433static void
1434pause_all_torrents (struct cbdata * data)
1435{
1436  tr_session * session = gtr_core_session (data->core);
1437  const char * cmd = "{ \"method\": \"torrent-stop\" }";
1438  tr_rpc_request_exec_json (session, cmd, strlen (cmd), NULL, NULL);
1439}
1440
1441static tr_torrent*
1442get_first_selected_torrent (struct cbdata * data)
1443{
1444  tr_torrent * tor = NULL;
1445  GtkTreeModel * m;
1446  GList * l = gtk_tree_selection_get_selected_rows (data->sel, &m);
1447  if (l != NULL)
1448    {
1449      GtkTreePath * p = l->data;
1450      GtkTreeIter i;
1451      if (gtk_tree_model_get_iter (m, &i, p))
1452        gtk_tree_model_get (m, &i, MC_TORRENT, &tor, -1);
1453    }
1454  g_list_foreach (l, (GFunc)gtk_tree_path_free, NULL);
1455  g_list_free (l);
1456  return tor;
1457}
1458
1459static void
1460copy_magnet_link_to_clipboard (GtkWidget * w, tr_torrent * tor)
1461{
1462  char * magnet = tr_torrentGetMagnetLink (tor);
1463  GdkDisplay * display = gtk_widget_get_display (w);
1464  GdkAtom selection;
1465  GtkClipboard * clipboard;
1466
1467  /* this is The Right Thing for copy/paste... */
1468  selection = GDK_SELECTION_CLIPBOARD;
1469  clipboard = gtk_clipboard_get_for_display (display, selection);
1470  gtk_clipboard_set_text (clipboard, magnet, -1);
1471
1472  /* ...but people using plain ol' X need this instead */
1473  selection = GDK_SELECTION_PRIMARY;
1474  clipboard = gtk_clipboard_get_for_display (display, selection);
1475  gtk_clipboard_set_text (clipboard, magnet, -1);
1476
1477  /* cleanup */
1478  tr_free (magnet);
1479}
1480
1481void
1482gtr_actions_handler (const char * action_name, gpointer user_data)
1483{
1484  gboolean changed = FALSE;
1485  struct cbdata * data = user_data;
1486
1487  if (!strcmp (action_name, "open-torrent-from-url"))
1488    {
1489      GtkWidget * w = gtr_torrent_open_from_url_dialog_new (data->wind, data->core);
1490      gtk_widget_show (w);
1491    }
1492  else if (!strcmp (action_name, "open-torrent-menu")
1493        || !strcmp (action_name, "open-torrent-toolbar"))
1494    {
1495      GtkWidget * w = gtr_torrent_open_from_file_dialog_new (data->wind, data->core);
1496      gtk_widget_show (w);
1497    }
1498    else if (!strcmp (action_name, "show-stats"))
1499    {
1500        GtkWidget * dialog = gtr_stats_dialog_new (data->wind, data->core);
1501        gtk_widget_show (dialog);
1502    }
1503  else if (!strcmp (action_name, "donate"))
1504    {
1505      gtr_open_uri ("http://www.transmissionbt.com/donate.php");
1506    }
1507  else if (!strcmp (action_name, "pause-all-torrents"))
1508    {
1509      pause_all_torrents (data);
1510    }
1511  else if (!strcmp (action_name, "start-all-torrents"))
1512    {
1513      start_all_torrents (data);
1514    }
1515  else if (!strcmp (action_name, "copy-magnet-link-to-clipboard"))
1516    {
1517      tr_torrent * tor = get_first_selected_torrent (data);
1518      if (tor != NULL)
1519        {
1520          copy_magnet_link_to_clipboard (GTK_WIDGET (data->wind), tor);
1521        }
1522    }
1523  else if (!strcmp (action_name, "relocate-torrent"))
1524    {
1525      GSList * ids = get_selected_torrent_ids (data);
1526      if (ids != NULL)
1527        {
1528          GtkWindow * parent = data->wind;
1529          GtkWidget * w = gtr_relocate_dialog_new (parent, data->core, ids);
1530          gtk_widget_show (w);
1531        }
1532    }
1533  else if (!strcmp (action_name, "torrent-start")
1534        || !strcmp (action_name, "torrent-start-now")
1535        || !strcmp (action_name, "torrent-stop")
1536        || !strcmp (action_name, "torrent-reannounce")
1537        || !strcmp (action_name, "torrent-verify")
1538        || !strcmp (action_name, "queue-move-top")
1539        || !strcmp (action_name, "queue-move-up")
1540        || !strcmp (action_name, "queue-move-down")
1541        || !strcmp (action_name, "queue-move-bottom"))
1542    {
1543      changed |= call_rpc_for_selected_torrents (data, action_name);
1544    }
1545  else if (!strcmp (action_name, "open-torrent-folder"))
1546    {
1547      gtk_tree_selection_selected_foreach (data->sel, open_folder_foreach, data->core);
1548    }
1549  else if (!strcmp (action_name, "show-torrent-properties"))
1550    {
1551      show_details_dialog_for_selected_torrents (data);
1552    }
1553  else if (!strcmp (action_name, "new-torrent"))
1554    {
1555      GtkWidget * w = gtr_torrent_creation_dialog_new (data->wind, data->core);
1556      gtk_widget_show (w);
1557    }
1558  else if (!strcmp (action_name, "remove-torrent"))
1559    {
1560      remove_selected (data, FALSE);
1561    }
1562  else if (!strcmp (action_name, "delete-torrent"))
1563    {
1564      remove_selected (data, TRUE);
1565    }
1566  else if (!strcmp (action_name, "quit"))
1567    {
1568      on_app_exit (data);
1569    }
1570  else if (!strcmp (action_name, "select-all"))
1571    {
1572      gtk_tree_selection_select_all (data->sel);
1573    }
1574  else if (!strcmp (action_name, "deselect-all"))
1575    {
1576      gtk_tree_selection_unselect_all (data->sel);
1577    }
1578  else if (!strcmp (action_name, "edit-preferences"))
1579    {
1580      if (NULL == data->prefs)
1581        {
1582          data->prefs = gtr_prefs_dialog_new (data->wind, G_OBJECT (data->core));
1583          g_signal_connect (data->prefs, "destroy",
1584                            G_CALLBACK (gtk_widget_destroyed), &data->prefs);
1585        }
1586        gtr_window_present (GTK_WINDOW (data->prefs));
1587    }
1588  else if (!strcmp (action_name, "toggle-message-log"))
1589    {
1590      if (!data->msgwin)
1591        {
1592          GtkWidget * win = gtr_message_log_window_new (data->wind, data->core);
1593          g_signal_connect (win, "destroy", G_CALLBACK (on_message_window_closed), NULL);
1594          data->msgwin = win;
1595        }
1596      else
1597        {
1598          gtr_action_set_toggled ("toggle-message-log", FALSE);
1599          gtk_widget_destroy (data->msgwin);
1600          data->msgwin = NULL;
1601        }
1602    }
1603  else if (!strcmp (action_name, "show-about-dialog"))
1604    {
1605      show_about_dialog (data->wind);
1606    }
1607  else if (!strcmp (action_name, "help"))
1608    {
1609      gtr_open_uri (gtr_get_help_uri ());
1610    }
1611  else if (!strcmp (action_name, "toggle-main-window"))
1612    {
1613      toggleMainWindow (data);
1614    }
1615  else if (!strcmp (action_name, "present-main-window"))
1616    {
1617      presentMainWindow (data);
1618    }
1619  else
1620    {
1621      g_error ("Unhandled action: %s", action_name);
1622    }
1623
1624  if (changed)
1625    update_model_soon (data);
1626}
Note: See TracBrowser for help on using the repository browser.