source: trunk/gtk/main.c @ 14112

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

fix a minor GAppInfo memory leak in transmission-gtk

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