source: trunk/gtk/main.c @ 14581

Last change on this file since 14581 was 14581, checked in by mikedld, 6 years ago

Remove unused session tag

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