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

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

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

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