source: trunk/gtk/main.c @ 14689

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

Update user-facing copyright years

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