source: trunk/gtk/main.c @ 14670

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

#6051: transmission-gtk --version shouldn't require display

  • Property svn:keywords set to Date Rev Author Id
File size: 50.1 KB
Line 
1/******************************************************************************
2 * $Id: main.c 14670 2016-01-09 18:06:17Z 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  g_set_application_name (_("Transmission"));
647
648  /* parse the command line */
649  option_context = g_option_context_new (_("[torrent files or urls]"));
650  g_option_context_add_main_entries (option_context, option_entries, GETTEXT_PACKAGE);
651  g_option_context_add_group (option_context, gtk_get_option_group (FALSE));
652  g_option_context_set_translation_domain (option_context, GETTEXT_PACKAGE);
653  if (!g_option_context_parse (option_context, &argc, &argv, &error))
654    {
655      g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"), error->message, argv[0]);
656      g_error_free (error);
657      g_option_context_free (option_context);
658      return 1;
659    }
660  g_option_context_free (option_context);
661
662  /* handle the trivial "version" option */
663  if (show_version)
664    {
665      fprintf (stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING);
666      return 0;
667    }
668
669  gtk_window_set_default_icon_name (MY_CONFIG_NAME);
670
671  /* init the unit formatters */
672  tr_formatter_mem_init (mem_K, _ (mem_K_str), _ (mem_M_str), _ (mem_G_str), _ (mem_T_str));
673  tr_formatter_size_init (disk_K, _ (disk_K_str), _ (disk_M_str), _ (disk_G_str), _ (disk_T_str));
674  tr_formatter_speed_init (speed_K, _ (speed_K_str), _ (speed_M_str), _ (speed_G_str), _ (speed_T_str));
675
676  /* set up the config dir */
677  gtr_pref_init (cbdata.config_dir);
678  g_mkdir_with_parents (cbdata.config_dir, 0755);
679
680  /* init notifications */
681  gtr_notify_init ();
682
683  /* init the application for the specified config dir */
684  stat (cbdata.config_dir, &sb);
685  application_id = g_strdup_printf ("com.transmissionbt.transmission_%lu_%lu", (unsigned long)sb.st_dev, (unsigned long)sb.st_ino);
686  app = gtk_application_new (application_id, G_APPLICATION_HANDLES_OPEN);
687  g_signal_connect (app, "open", G_CALLBACK (on_open), &cbdata);
688  g_signal_connect (app, "startup", G_CALLBACK (on_startup), &cbdata);
689  g_signal_connect (app, "activate", G_CALLBACK (on_activate), &cbdata);
690  ret = g_application_run (G_APPLICATION (app), argc, argv);
691  g_object_unref (app);
692  g_free (application_id);
693  return ret;
694}
695
696static void
697on_core_busy (TrCore * core UNUSED, gboolean busy, struct cbdata * c)
698{
699  gtr_window_set_busy (c->wind, busy);
700}
701
702static void on_core_error (TrCore *, guint, const char *, struct cbdata *);
703static void on_add_torrent (TrCore *, tr_ctor *, gpointer);
704static void on_prefs_changed (TrCore * core, const tr_quark key, gpointer);
705static void main_window_setup (struct cbdata * cbdata, GtkWindow * wind);
706static gboolean update_model_loop (gpointer gdata);
707static gboolean update_model_once (gpointer gdata);
708
709static void
710app_setup (GtkWindow * wind, struct cbdata * cbdata)
711{
712  if (cbdata->is_iconified)
713    gtr_pref_flag_set (TR_KEY_show_notification_area_icon, TRUE);
714
715  gtr_actions_set_core (cbdata->core);
716
717  /* set up core handlers */
718  g_signal_connect (cbdata->core, "busy", G_CALLBACK (on_core_busy), cbdata);
719  g_signal_connect (cbdata->core, "add-error", G_CALLBACK (on_core_error), cbdata);
720  g_signal_connect (cbdata->core, "add-prompt", G_CALLBACK (on_add_torrent), cbdata);
721  g_signal_connect (cbdata->core, "prefs-changed", G_CALLBACK (on_prefs_changed), cbdata);
722
723  /* add torrents from command-line and saved state */
724  gtr_core_load (cbdata->core, cbdata->start_paused);
725  gtr_core_torrents_added (cbdata->core);
726
727  /* set up main window */
728  main_window_setup (cbdata, wind);
729
730  /* set up the icon */
731  on_prefs_changed (cbdata->core, TR_KEY_show_notification_area_icon, cbdata);
732
733  /* start model update timer */
734  cbdata->timer = gdk_threads_add_timeout_seconds (MAIN_WINDOW_REFRESH_INTERVAL_SECONDS, update_model_loop, cbdata);
735  update_model_once (cbdata);
736
737  /* either show the window or iconify it */
738  if (!cbdata->is_iconified)
739    {
740      gtk_widget_show (GTK_WIDGET (wind));
741    }
742  else
743    {
744      gtk_window_set_skip_taskbar_hint (cbdata->wind,
745                                        cbdata->icon != NULL);
746      cbdata->is_iconified = FALSE; // ensure that the next toggle iconifies
747      gtr_action_set_toggled ("toggle-main-window", FALSE);
748    }
749
750  if (!gtr_pref_flag_get (TR_KEY_user_has_given_informed_consent))
751    {
752      GtkWidget * w = gtk_message_dialog_new (GTK_WINDOW (wind),
753                                              GTK_DIALOG_DESTROY_WITH_PARENT,
754                                              GTK_MESSAGE_OTHER,
755                                              GTK_BUTTONS_NONE,
756                                              "%s",
757        _("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."));
758      gtk_dialog_add_button (GTK_DIALOG (w), GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT);
759      gtk_dialog_add_button (GTK_DIALOG (w), _("I _Agree"), GTK_RESPONSE_ACCEPT);
760      gtk_dialog_set_default_response (GTK_DIALOG (w), GTK_RESPONSE_ACCEPT);
761      switch (gtk_dialog_run (GTK_DIALOG (w)))
762        {
763          case GTK_RESPONSE_ACCEPT:
764            /* only show it once */
765            gtr_pref_flag_set (TR_KEY_user_has_given_informed_consent, TRUE);
766            gtk_widget_destroy (w);
767            break;
768
769          default:
770            exit (0);
771        }
772    }
773}
774
775static void
776presentMainWindow (struct cbdata * cbdata)
777{
778  GtkWindow * window = cbdata->wind;
779
780  if (cbdata->is_iconified)
781    {
782      cbdata->is_iconified = false;
783
784      gtk_window_set_skip_taskbar_hint (window, FALSE);
785    }
786
787  if (!gtk_widget_get_visible (GTK_WIDGET (window)))
788    {
789      gtk_window_resize (window, gtr_pref_int_get (TR_KEY_main_window_width),
790                                 gtr_pref_int_get (TR_KEY_main_window_height));
791      gtk_window_move (window, gtr_pref_int_get (TR_KEY_main_window_x),
792                               gtr_pref_int_get (TR_KEY_main_window_y));
793      gtr_widget_set_visible (GTK_WIDGET (window), TRUE);
794    }
795
796  gtr_window_present (window);
797  gdk_window_raise (gtk_widget_get_window (GTK_WIDGET(window)));
798}
799
800static void
801hideMainWindow (struct cbdata * cbdata)
802{
803  GtkWindow * window = cbdata->wind;
804  gtk_window_set_skip_taskbar_hint (window, TRUE);
805  gtr_widget_set_visible (GTK_WIDGET (window), FALSE);
806  cbdata->is_iconified = true;
807}
808
809static void
810toggleMainWindow (struct cbdata * cbdata)
811{
812  if (cbdata->is_iconified)
813    presentMainWindow (cbdata);
814  else
815    hideMainWindow (cbdata);
816}
817
818static void on_app_exit (gpointer vdata);
819
820static gboolean
821winclose (GtkWidget * w    UNUSED,
822          GdkEvent * event UNUSED,
823          gpointer         gdata)
824{
825  struct cbdata * cbdata = gdata;
826
827  if (cbdata->icon != NULL)
828    gtr_action_activate ("toggle-main-window");
829  else
830    on_app_exit (cbdata);
831
832  return TRUE; /* don't propagate event further */
833}
834
835static void
836rowChangedCB (GtkTreeModel  * model UNUSED,
837              GtkTreePath   * path,
838              GtkTreeIter   * iter  UNUSED,
839              gpointer        gdata)
840{
841  struct cbdata * data = gdata;
842
843  if (gtk_tree_selection_path_is_selected (data->sel, path))
844    refresh_actions_soon (data);
845}
846
847static void
848on_drag_data_received (GtkWidget         * widget          UNUSED,
849                       GdkDragContext    * drag_context,
850                       gint                x               UNUSED,
851                       gint                y               UNUSED,
852                       GtkSelectionData  * selection_data,
853                       guint               info            UNUSED,
854                       guint               time_,
855                       gpointer            gdata)
856{
857  guint i;
858  char ** uris = gtk_selection_data_get_uris (selection_data);
859  const guint file_count = g_strv_length (uris);
860  GSList * files = NULL;
861
862  for (i=0; i<file_count; ++i)
863    files = g_slist_prepend (files, g_file_new_for_uri (uris[i]));
864
865  open_files (files, gdata);
866
867  /* cleanup */
868  g_slist_foreach (files, (GFunc)g_object_unref, NULL);
869  g_slist_free (files);
870  g_strfreev (uris);
871
872  gtk_drag_finish (drag_context, true, FALSE, time_);
873}
874
875static void
876main_window_setup (struct cbdata * cbdata, GtkWindow * wind)
877{
878  GtkWidget * w;
879  GtkTreeModel * model;
880  GtkTreeSelection * sel;
881
882  g_assert (NULL == cbdata->wind);
883  cbdata->wind = wind;
884  cbdata->sel = sel = GTK_TREE_SELECTION (gtr_window_get_selection (cbdata->wind));
885
886  g_signal_connect (sel, "changed", G_CALLBACK (on_selection_changed), cbdata);
887  on_selection_changed (sel, cbdata);
888  model = gtr_core_model (cbdata->core);
889  g_signal_connect (model, "row-changed", G_CALLBACK (rowChangedCB), cbdata);
890  g_signal_connect (wind, "delete-event", G_CALLBACK (winclose), cbdata);
891  refresh_actions (cbdata);
892
893  /* register to handle URIs that get dragged onto our main window */
894  w = GTK_WIDGET (wind);
895  gtk_drag_dest_set (w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
896  gtk_drag_dest_add_uri_targets (w);
897  g_signal_connect (w, "drag-data-received", G_CALLBACK (on_drag_data_received), cbdata);
898}
899
900static gboolean
901on_session_closed (gpointer gdata)
902{
903  GSList * tmp;
904  struct cbdata * cbdata = gdata;
905
906  tmp = g_slist_copy (cbdata->details);
907  g_slist_foreach (tmp, (GFunc)gtk_widget_destroy, NULL);
908  g_slist_free (tmp);
909
910  if (cbdata->prefs)
911    gtk_widget_destroy (GTK_WIDGET (cbdata->prefs));
912  if (cbdata->wind)
913    gtk_widget_destroy (GTK_WIDGET (cbdata->wind));
914  g_object_unref (cbdata->core);
915  if (cbdata->icon)
916    g_object_unref (cbdata->icon);
917  g_slist_foreach (cbdata->error_list, (GFunc)g_free, NULL);
918  g_slist_free (cbdata->error_list);
919  g_slist_foreach (cbdata->duplicates_list, (GFunc)g_free, NULL);
920  g_slist_free (cbdata->duplicates_list);
921
922  return G_SOURCE_REMOVE;
923}
924
925struct session_close_struct
926{
927  tr_session * session;
928  struct cbdata * cbdata;
929};
930
931/* since tr_sessionClose () is a blocking function,
932 * delegate its call to another thread here... when it's done,
933 * punt the GUI teardown back to the GTK+ thread */
934static gpointer
935session_close_threadfunc (gpointer gdata)
936{
937  struct session_close_struct * data = gdata;
938  tr_sessionClose (data->session);
939  gdk_threads_add_idle (on_session_closed, data->cbdata);
940  g_free (data);
941  return NULL;
942}
943
944static void
945exit_now_cb (GtkWidget *w UNUSED, gpointer data UNUSED)
946{
947  exit (0);
948}
949
950static void
951on_app_exit (gpointer vdata)
952{
953  GtkWidget *r, *p, *b, *w, *c;
954  struct cbdata * cbdata = vdata;
955  struct session_close_struct * session_close_data;
956
957  if (cbdata->is_closing)
958    return;
959
960  cbdata->is_closing = true;
961
962  /* stop the update timer */
963  if (cbdata->timer)
964    {
965      g_source_remove (cbdata->timer);
966      cbdata->timer = 0;
967    }
968
969  /* stop the refresh-actions timer */
970  if (cbdata->refresh_actions_tag)
971    {
972      g_source_remove (cbdata->refresh_actions_tag);
973      cbdata->refresh_actions_tag = 0;
974    }
975
976  c = GTK_WIDGET (cbdata->wind);
977  gtk_container_remove (GTK_CONTAINER (c), gtk_bin_get_child (GTK_BIN (c)));
978
979  r = gtk_alignment_new (0.5, 0.5, 0.01, 0.01);
980  gtk_container_add (GTK_CONTAINER (c), r);
981
982  p = gtk_grid_new ();
983  gtk_grid_set_column_spacing (GTK_GRID (p), GUI_PAD_BIG);
984  gtk_container_add (GTK_CONTAINER (r), p);
985
986  w = gtk_image_new_from_stock (GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG);
987  gtk_grid_attach (GTK_GRID (p), w, 0, 0, 1, 2);
988
989  w = gtk_label_new (NULL);
990  gtk_label_set_markup (GTK_LABEL (w), _("<b>Closing Connections</b>"));
991  gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
992  gtk_grid_attach (GTK_GRID (p), w, 1, 0, 1, 1);
993
994  w = gtk_label_new (_("Sending upload/download totals to tracker
"));
995  gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
996  gtk_grid_attach (GTK_GRID (p), w, 1, 1, 1, 1);
997
998  b = gtk_alignment_new (0.0, 1.0, 0.01, 0.01);
999  w = gtk_button_new_with_mnemonic (_("_Quit Now"));
1000  g_signal_connect (w, "clicked", G_CALLBACK (exit_now_cb), NULL);
1001  gtk_container_add (GTK_CONTAINER (b), w);
1002  gtk_grid_attach (GTK_GRID (p), b, 1, 2, 1, 1);
1003
1004  gtk_widget_show_all (r);
1005  gtk_widget_grab_focus (w);
1006
1007  /* clear the UI */
1008  gtr_core_clear (cbdata->core);
1009
1010  /* ensure the window is in its previous position & size.
1011   * this seems to be necessary because changing the main window's
1012   * child seems to unset the size */
1013  gtk_window_resize (cbdata->wind, gtr_pref_int_get (TR_KEY_main_window_width),
1014                                   gtr_pref_int_get (TR_KEY_main_window_height));
1015  gtk_window_move (cbdata->wind, gtr_pref_int_get (TR_KEY_main_window_x),
1016                                 gtr_pref_int_get (TR_KEY_main_window_y));
1017
1018  /* shut down libT */
1019  session_close_data = g_new (struct session_close_struct, 1);
1020  session_close_data->cbdata = cbdata;
1021  session_close_data->session = gtr_core_close (cbdata->core);
1022  g_thread_new ("shutdown-thread", session_close_threadfunc, session_close_data);
1023}
1024
1025static void
1026show_torrent_errors (GtkWindow * window, const char * primary, GSList ** files)
1027{
1028  GSList * l;
1029  GtkWidget * w;
1030  GString * s = g_string_new (NULL);
1031  const char * leader = g_slist_length (*files) > 1
1032                      ? gtr_get_unicode_string (GTR_UNICODE_BULLET)
1033                      : "";
1034
1035  for (l=*files; l!=NULL; l=l->next)
1036    g_string_append_printf (s, "%s %s\n", leader, (const char*)l->data);
1037
1038  w = gtk_message_dialog_new (window,
1039                              GTK_DIALOG_DESTROY_WITH_PARENT,
1040                              GTK_MESSAGE_ERROR,
1041                              GTK_BUTTONS_CLOSE,
1042                              "%s", primary);
1043  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (w),
1044                                            "%s", s->str);
1045  g_signal_connect_swapped (w, "response",
1046                            G_CALLBACK (gtk_widget_destroy), w);
1047  gtk_widget_show (w);
1048  g_string_free (s, TRUE);
1049
1050  g_slist_foreach (*files, (GFunc)g_free, NULL);
1051  g_slist_free (*files);
1052  *files = NULL;
1053}
1054
1055static void
1056flush_torrent_errors (struct cbdata * cbdata)
1057{
1058  if (cbdata->error_list)
1059    show_torrent_errors (cbdata->wind,
1060                         ngettext ("Couldn't add corrupt torrent",
1061                                   "Couldn't add corrupt torrents",
1062                                   g_slist_length (cbdata->error_list)),
1063                         &cbdata->error_list);
1064
1065  if (cbdata->duplicates_list)
1066    show_torrent_errors (cbdata->wind,
1067                         ngettext ("Couldn't add duplicate torrent",
1068                                   "Couldn't add duplicate torrents",
1069                                   g_slist_length (cbdata->duplicates_list)),
1070                         &cbdata->duplicates_list);
1071}
1072
1073static void
1074on_core_error (TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c)
1075{
1076  switch (code)
1077    {
1078      case TR_PARSE_ERR:
1079        c->error_list = g_slist_append (c->error_list, g_path_get_basename (msg));
1080        break;
1081
1082      case TR_PARSE_DUPLICATE:
1083        c->duplicates_list = g_slist_append (c->duplicates_list, g_strdup (msg));
1084        break;
1085
1086      case TR_CORE_ERR_NO_MORE_TORRENTS:
1087        flush_torrent_errors (c);
1088        break;
1089
1090      default:
1091        g_assert_not_reached ();
1092        break;
1093    }
1094}
1095
1096static gboolean
1097on_main_window_focus_in (GtkWidget      * widget UNUSED,
1098                         GdkEventFocus  * event  UNUSED,
1099                         gpointer         gdata)
1100{
1101  struct cbdata * cbdata = gdata;
1102
1103  if (cbdata->wind)
1104    gtk_window_set_urgency_hint (cbdata->wind, FALSE);
1105
1106  return FALSE;
1107}
1108
1109static void
1110on_add_torrent (TrCore * core, tr_ctor * ctor, gpointer gdata)
1111{
1112  struct cbdata * cbdata = gdata;
1113  GtkWidget * w = gtr_torrent_options_dialog_new (cbdata->wind, core, ctor);
1114
1115  g_signal_connect (w, "focus-in-event",
1116                    G_CALLBACK (on_main_window_focus_in),  cbdata);
1117  if (cbdata->wind)
1118    gtk_window_set_urgency_hint (cbdata->wind, TRUE);
1119
1120  gtk_widget_show (w);
1121}
1122
1123static void
1124on_prefs_changed (TrCore * core UNUSED, const tr_quark key, gpointer data)
1125{
1126  struct cbdata * cbdata = data;
1127  tr_session * tr = gtr_core_session (cbdata->core);
1128
1129  switch (key)
1130    {
1131      case TR_KEY_encryption:
1132        tr_sessionSetEncryption (tr, gtr_pref_int_get (key));
1133        break;
1134
1135      case TR_KEY_download_dir:
1136        tr_sessionSetDownloadDir (tr, gtr_pref_string_get (key));
1137        break;
1138
1139      case TR_KEY_message_level:
1140        tr_logSetLevel (gtr_pref_int_get (key));
1141        break;
1142
1143      case TR_KEY_peer_port:
1144        tr_sessionSetPeerPort (tr, gtr_pref_int_get (key));
1145        break;
1146
1147      case TR_KEY_blocklist_enabled:
1148        tr_blocklistSetEnabled (tr, gtr_pref_flag_get (key));
1149        break;
1150
1151      case TR_KEY_blocklist_url:
1152        tr_blocklistSetURL (tr, gtr_pref_string_get (key));
1153        break;
1154
1155      case TR_KEY_show_notification_area_icon:
1156        {
1157          const bool show = gtr_pref_flag_get (key);
1158          if (show && !cbdata->icon)
1159            cbdata->icon = gtr_icon_new (cbdata->core);
1160          else if (!show && cbdata->icon)
1161            g_clear_object (&cbdata->icon);
1162          break;
1163        }
1164
1165      case TR_KEY_speed_limit_down_enabled:
1166        tr_sessionLimitSpeed (tr, TR_DOWN, gtr_pref_flag_get (key));
1167        break;
1168
1169      case TR_KEY_speed_limit_down:
1170        tr_sessionSetSpeedLimit_KBps (tr, TR_DOWN, gtr_pref_int_get (key));
1171        break;
1172
1173      case TR_KEY_speed_limit_up_enabled:
1174        tr_sessionLimitSpeed (tr, TR_UP, gtr_pref_flag_get (key));
1175        break;
1176
1177      case TR_KEY_speed_limit_up:
1178        tr_sessionSetSpeedLimit_KBps (tr, TR_UP, gtr_pref_int_get (key));
1179        break;
1180
1181      case TR_KEY_ratio_limit_enabled:
1182        tr_sessionSetRatioLimited (tr, gtr_pref_flag_get (key));
1183        break;
1184
1185      case TR_KEY_ratio_limit:
1186        tr_sessionSetRatioLimit (tr, gtr_pref_double_get (key));
1187        break;
1188
1189      case TR_KEY_idle_seeding_limit:
1190        tr_sessionSetIdleLimit (tr, gtr_pref_int_get (key));
1191        break;
1192
1193      case TR_KEY_idle_seeding_limit_enabled:
1194        tr_sessionSetIdleLimited (tr, gtr_pref_flag_get (key));
1195        break;
1196
1197      case TR_KEY_port_forwarding_enabled:
1198        tr_sessionSetPortForwardingEnabled (tr, gtr_pref_flag_get (key));
1199        break;
1200
1201      case TR_KEY_pex_enabled:
1202        tr_sessionSetPexEnabled (tr, gtr_pref_flag_get (key));
1203        break;
1204
1205      case TR_KEY_rename_partial_files:
1206        tr_sessionSetIncompleteFileNamingEnabled (tr, gtr_pref_flag_get (key));
1207        break;
1208
1209      case TR_KEY_download_queue_size:
1210        tr_sessionSetQueueSize (tr, TR_DOWN, gtr_pref_int_get (key));
1211        break;
1212
1213      case TR_KEY_queue_stalled_minutes:
1214        tr_sessionSetQueueStalledMinutes (tr, gtr_pref_int_get (key));
1215        break;
1216
1217      case TR_KEY_dht_enabled:
1218        tr_sessionSetDHTEnabled (tr, gtr_pref_flag_get (key));
1219        break;
1220
1221      case TR_KEY_utp_enabled:
1222        tr_sessionSetUTPEnabled (tr, gtr_pref_flag_get (key));
1223        break;
1224
1225      case TR_KEY_lpd_enabled:
1226        tr_sessionSetLPDEnabled (tr, gtr_pref_flag_get (key));
1227        break;
1228
1229      case TR_KEY_rpc_port:
1230        tr_sessionSetRPCPort (tr, gtr_pref_int_get (key));
1231        break;
1232
1233      case TR_KEY_rpc_enabled:
1234        tr_sessionSetRPCEnabled (tr, gtr_pref_flag_get (key));
1235        break;
1236
1237      case TR_KEY_rpc_whitelist:
1238        tr_sessionSetRPCWhitelist (tr, gtr_pref_string_get (key));
1239        break;
1240
1241      case TR_KEY_rpc_whitelist_enabled:
1242        tr_sessionSetRPCWhitelistEnabled (tr, gtr_pref_flag_get (key));
1243        break;
1244
1245      case TR_KEY_rpc_username:
1246        tr_sessionSetRPCUsername (tr, gtr_pref_string_get (key));
1247        break;
1248
1249      case TR_KEY_rpc_password:
1250        tr_sessionSetRPCPassword (tr, gtr_pref_string_get (key));
1251        break;
1252
1253      case TR_KEY_rpc_authentication_required:
1254        tr_sessionSetRPCPasswordEnabled (tr, gtr_pref_flag_get (key));
1255        break;
1256
1257      case TR_KEY_alt_speed_up:
1258        tr_sessionSetAltSpeed_KBps (tr, TR_UP, gtr_pref_int_get (key));
1259        break;
1260
1261      case TR_KEY_alt_speed_down:
1262        tr_sessionSetAltSpeed_KBps (tr, TR_DOWN, gtr_pref_int_get (key));
1263        break;
1264
1265      case TR_KEY_alt_speed_enabled:
1266        {
1267          const bool b = gtr_pref_flag_get (key);
1268          tr_sessionUseAltSpeed (tr, b);
1269          gtr_action_set_toggled (tr_quark_get_string(key,NULL), b);
1270          break;
1271        }
1272
1273      case TR_KEY_alt_speed_time_begin:
1274        tr_sessionSetAltSpeedBegin (tr, gtr_pref_int_get (key));
1275        break;
1276
1277      case TR_KEY_alt_speed_time_end:
1278        tr_sessionSetAltSpeedEnd (tr, gtr_pref_int_get (key));
1279        break;
1280
1281      case TR_KEY_alt_speed_time_enabled:
1282        tr_sessionUseAltSpeedTime (tr, gtr_pref_flag_get (key));
1283        break;
1284
1285      case TR_KEY_alt_speed_time_day:
1286        tr_sessionSetAltSpeedDay (tr, gtr_pref_int_get (key));
1287        break;
1288
1289      case TR_KEY_peer_port_random_on_start:
1290        tr_sessionSetPeerPortRandomOnStart (tr, gtr_pref_flag_get (key));
1291        break;
1292
1293      case TR_KEY_incomplete_dir:
1294        tr_sessionSetIncompleteDir (tr, gtr_pref_string_get (key));
1295        break;
1296
1297      case TR_KEY_incomplete_dir_enabled:
1298        tr_sessionSetIncompleteDirEnabled (tr, gtr_pref_flag_get (key));
1299        break;
1300
1301      case TR_KEY_script_torrent_done_enabled:
1302        tr_sessionSetTorrentDoneScriptEnabled (tr, gtr_pref_flag_get (key));
1303        break;
1304
1305      case TR_KEY_script_torrent_done_filename:
1306        tr_sessionSetTorrentDoneScript (tr, gtr_pref_string_get (key));
1307        break;
1308
1309      case TR_KEY_start_added_torrents:
1310        tr_sessionSetPaused (tr, !gtr_pref_flag_get (key));
1311        break;
1312
1313      case TR_KEY_trash_original_torrent_files:
1314        tr_sessionSetDeleteSource (tr, gtr_pref_flag_get (key));
1315        break;
1316
1317      default:
1318        break;
1319    }
1320}
1321
1322static gboolean
1323update_model_once (gpointer gdata)
1324{
1325  struct cbdata *data = gdata;
1326
1327  /* update the torrent data in the model */
1328  gtr_core_update (data->core);
1329
1330  /* refresh the main window's statusbar and toolbar buttons */
1331  if (data->wind != NULL)
1332    gtr_window_refresh (data->wind);
1333
1334  /* update the actions */
1335  refresh_actions (data);
1336
1337  /* update the status tray icon */
1338  if (data->icon != NULL)
1339    gtr_icon_refresh (data->icon);
1340
1341  data->update_model_soon_tag = 0;
1342  return G_SOURCE_REMOVE;
1343}
1344
1345static void
1346update_model_soon (gpointer gdata)
1347{
1348  struct cbdata *data = gdata;
1349
1350  if (data->update_model_soon_tag == 0)
1351    data->update_model_soon_tag = gdk_threads_add_idle (update_model_once, data);
1352}
1353
1354static gboolean
1355update_model_loop (gpointer gdata)
1356{
1357  const gboolean done = global_sigcount;
1358
1359  if (!done)
1360    update_model_once (gdata);
1361
1362  return !done;
1363}
1364
1365static void
1366show_about_dialog (GtkWindow * parent)
1367{
1368  const char * uri = "http://www.transmissionbt.com/";
1369  const char * authors[] = { "Jordan Lee (Backend; GTK+)",
1370                             "Mitchell Livingston (Backend; OS X)",
1371                             NULL };
1372
1373  gtk_show_about_dialog (parent,
1374    "authors", authors,
1375    "comments", _("A fast and easy BitTorrent client"),
1376    "copyright", _("Copyright (c) The Transmission Project"),
1377    "logo-icon-name", MY_CONFIG_NAME,
1378    "name", g_get_application_name (),
1379    /* Translators: translate "translator-credits" as your name
1380       to have it appear in the credits in the "About"
1381       dialog */
1382    "translator-credits", _("translator-credits"),
1383    "version", LONG_VERSION_STRING,
1384    "website", uri,
1385    "website-label", uri,
1386#ifdef SHOW_LICENSE
1387    "license", LICENSE,
1388    "wrap-license", TRUE,
1389#endif
1390    NULL);
1391}
1392
1393static void
1394append_id_to_benc_list (GtkTreeModel * m, GtkTreePath * path UNUSED,
1395                        GtkTreeIter * iter, gpointer list)
1396{
1397  tr_torrent * tor = NULL;
1398  gtk_tree_model_get (m, iter, MC_TORRENT, &tor, -1);
1399  tr_variantListAddInt (list, tr_torrentId (tor));
1400}
1401
1402static gboolean
1403call_rpc_for_selected_torrents (struct cbdata * data, const char * method)
1404{
1405  tr_variant top, *args, *ids;
1406  gboolean invoked = FALSE;
1407  GtkTreeSelection * s = data->sel;
1408  tr_session * session = gtr_core_session (data->core);
1409
1410  tr_variantInitDict (&top, 2);
1411  tr_variantDictAddStr (&top, TR_KEY_method, method);
1412  args = tr_variantDictAddDict (&top, TR_KEY_arguments, 1);
1413  ids = tr_variantDictAddList (args, TR_KEY_ids, 0);
1414  gtk_tree_selection_selected_foreach (s, append_id_to_benc_list, ids);
1415
1416  if (tr_variantListSize (ids) != 0)
1417    {
1418      tr_rpc_request_exec_json (session, &top, NULL, NULL);
1419      invoked = TRUE;
1420    }
1421
1422  tr_variantFree (&top);
1423  return invoked;
1424}
1425
1426static void
1427open_folder_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED,
1428                     GtkTreeIter * iter, gpointer core)
1429{
1430  int id;
1431  gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1);
1432  gtr_core_open_folder (core, id);
1433}
1434
1435static gboolean
1436on_message_window_closed (void)
1437{
1438  gtr_action_set_toggled ("toggle-message-log", FALSE);
1439  return FALSE;
1440}
1441
1442static void
1443accumulate_selected_torrents (GtkTreeModel  * model, GtkTreePath   * path UNUSED,
1444                              GtkTreeIter   * iter, gpointer        gdata)
1445{
1446  int id;
1447  GSList ** data = gdata;
1448
1449  gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1);
1450  *data = g_slist_append (*data, GINT_TO_POINTER (id));
1451}
1452
1453static void
1454remove_selected (struct cbdata * data, gboolean delete_files)
1455{
1456  GSList * l = NULL;
1457
1458  gtk_tree_selection_selected_foreach (data->sel, accumulate_selected_torrents, &l);
1459
1460  if (l != NULL)
1461    gtr_confirm_remove (data->wind, data->core, l, delete_files);
1462}
1463
1464static void
1465start_all_torrents (struct cbdata * data)
1466{
1467  tr_session * session = gtr_core_session (data->core);
1468  tr_variant request;
1469
1470  tr_variantInitDict (&request, 1);
1471  tr_variantDictAddStr (&request, TR_KEY_method, "torrent-start");
1472  tr_rpc_request_exec_json (session, &request, NULL, NULL);
1473  tr_variantFree (&request);
1474}
1475
1476static void
1477pause_all_torrents (struct cbdata * data)
1478{
1479  tr_session * session = gtr_core_session (data->core);
1480  tr_variant request;
1481
1482  tr_variantInitDict (&request, 1);
1483  tr_variantDictAddStr (&request, TR_KEY_method, "torrent-stop");
1484  tr_rpc_request_exec_json (session, &request, NULL, NULL);
1485  tr_variantFree (&request);
1486}
1487
1488static tr_torrent*
1489get_first_selected_torrent (struct cbdata * data)
1490{
1491  tr_torrent * tor = NULL;
1492  GtkTreeModel * m;
1493  GList * l = gtk_tree_selection_get_selected_rows (data->sel, &m);
1494  if (l != NULL)
1495    {
1496      GtkTreePath * p = l->data;
1497      GtkTreeIter i;
1498      if (gtk_tree_model_get_iter (m, &i, p))
1499        gtk_tree_model_get (m, &i, MC_TORRENT, &tor, -1);
1500    }
1501  g_list_foreach (l, (GFunc)gtk_tree_path_free, NULL);
1502  g_list_free (l);
1503  return tor;
1504}
1505
1506static void
1507copy_magnet_link_to_clipboard (GtkWidget * w, tr_torrent * tor)
1508{
1509  char * magnet = tr_torrentGetMagnetLink (tor);
1510  GdkDisplay * display = gtk_widget_get_display (w);
1511  GdkAtom selection;
1512  GtkClipboard * clipboard;
1513
1514  /* this is The Right Thing for copy/paste... */
1515  selection = GDK_SELECTION_CLIPBOARD;
1516  clipboard = gtk_clipboard_get_for_display (display, selection);
1517  gtk_clipboard_set_text (clipboard, magnet, -1);
1518
1519  /* ...but people using plain ol' X need this instead */
1520  selection = GDK_SELECTION_PRIMARY;
1521  clipboard = gtk_clipboard_get_for_display (display, selection);
1522  gtk_clipboard_set_text (clipboard, magnet, -1);
1523
1524  /* cleanup */
1525  tr_free (magnet);
1526}
1527
1528void
1529gtr_actions_handler (const char * action_name, gpointer user_data)
1530{
1531  gboolean changed = FALSE;
1532  struct cbdata * data = user_data;
1533
1534  if (!g_strcmp0 (action_name, "open-torrent-from-url"))
1535    {
1536      GtkWidget * w = gtr_torrent_open_from_url_dialog_new (data->wind, data->core);
1537      gtk_widget_show (w);
1538    }
1539  else if (!g_strcmp0 (action_name, "open-torrent-menu")
1540        || !g_strcmp0 (action_name, "open-torrent-toolbar"))
1541    {
1542      GtkWidget * w = gtr_torrent_open_from_file_dialog_new (data->wind, data->core);
1543      gtk_widget_show (w);
1544    }
1545    else if (!g_strcmp0 (action_name, "show-stats"))
1546    {
1547        GtkWidget * dialog = gtr_stats_dialog_new (data->wind, data->core);
1548        gtk_widget_show (dialog);
1549    }
1550  else if (!g_strcmp0 (action_name, "donate"))
1551    {
1552      gtr_open_uri ("http://www.transmissionbt.com/donate.php");
1553    }
1554  else if (!g_strcmp0 (action_name, "pause-all-torrents"))
1555    {
1556      pause_all_torrents (data);
1557    }
1558  else if (!g_strcmp0 (action_name, "start-all-torrents"))
1559    {
1560      start_all_torrents (data);
1561    }
1562  else if (!g_strcmp0 (action_name, "copy-magnet-link-to-clipboard"))
1563    {
1564      tr_torrent * tor = get_first_selected_torrent (data);
1565      if (tor != NULL)
1566        {
1567          copy_magnet_link_to_clipboard (GTK_WIDGET (data->wind), tor);
1568        }
1569    }
1570  else if (!g_strcmp0 (action_name, "relocate-torrent"))
1571    {
1572      GSList * ids = get_selected_torrent_ids (data);
1573      if (ids != NULL)
1574        {
1575          GtkWindow * parent = data->wind;
1576          GtkWidget * w = gtr_relocate_dialog_new (parent, data->core, ids);
1577          gtk_widget_show (w);
1578        }
1579    }
1580  else if (!g_strcmp0 (action_name, "torrent-start")
1581        || !g_strcmp0 (action_name, "torrent-start-now")
1582        || !g_strcmp0 (action_name, "torrent-stop")
1583        || !g_strcmp0 (action_name, "torrent-reannounce")
1584        || !g_strcmp0 (action_name, "torrent-verify")
1585        || !g_strcmp0 (action_name, "queue-move-top")
1586        || !g_strcmp0 (action_name, "queue-move-up")
1587        || !g_strcmp0 (action_name, "queue-move-down")
1588        || !g_strcmp0 (action_name, "queue-move-bottom"))
1589    {
1590      changed |= call_rpc_for_selected_torrents (data, action_name);
1591    }
1592  else if (!g_strcmp0 (action_name, "open-torrent-folder"))
1593    {
1594      gtk_tree_selection_selected_foreach (data->sel, open_folder_foreach, data->core);
1595    }
1596  else if (!g_strcmp0 (action_name, "show-torrent-properties"))
1597    {
1598      show_details_dialog_for_selected_torrents (data);
1599    }
1600  else if (!g_strcmp0 (action_name, "new-torrent"))
1601    {
1602      GtkWidget * w = gtr_torrent_creation_dialog_new (data->wind, data->core);
1603      gtk_widget_show (w);
1604    }
1605  else if (!g_strcmp0 (action_name, "remove-torrent"))
1606    {
1607      remove_selected (data, FALSE);
1608    }
1609  else if (!g_strcmp0 (action_name, "delete-torrent"))
1610    {
1611      remove_selected (data, TRUE);
1612    }
1613  else if (!g_strcmp0 (action_name, "quit"))
1614    {
1615      on_app_exit (data);
1616    }
1617  else if (!g_strcmp0 (action_name, "select-all"))
1618    {
1619      gtk_tree_selection_select_all (data->sel);
1620    }
1621  else if (!g_strcmp0 (action_name, "deselect-all"))
1622    {
1623      gtk_tree_selection_unselect_all (data->sel);
1624    }
1625  else if (!g_strcmp0 (action_name, "edit-preferences"))
1626    {
1627      if (NULL == data->prefs)
1628        {
1629          data->prefs = gtr_prefs_dialog_new (data->wind, G_OBJECT (data->core));
1630          g_signal_connect (data->prefs, "destroy",
1631                            G_CALLBACK (gtk_widget_destroyed), &data->prefs);
1632        }
1633        gtr_window_present (GTK_WINDOW (data->prefs));
1634    }
1635  else if (!g_strcmp0 (action_name, "toggle-message-log"))
1636    {
1637      if (!data->msgwin)
1638        {
1639          GtkWidget * win = gtr_message_log_window_new (data->wind, data->core);
1640          g_signal_connect (win, "destroy", G_CALLBACK (on_message_window_closed), NULL);
1641          data->msgwin = win;
1642        }
1643      else
1644        {
1645          gtr_action_set_toggled ("toggle-message-log", FALSE);
1646          gtk_widget_destroy (data->msgwin);
1647          data->msgwin = NULL;
1648        }
1649    }
1650  else if (!g_strcmp0 (action_name, "show-about-dialog"))
1651    {
1652      show_about_dialog (data->wind);
1653    }
1654  else if (!g_strcmp0 (action_name, "help"))
1655    {
1656      gtr_open_uri (gtr_get_help_uri ());
1657    }
1658  else if (!g_strcmp0 (action_name, "toggle-main-window"))
1659    {
1660      toggleMainWindow (data);
1661    }
1662  else if (!g_strcmp0 (action_name, "present-main-window"))
1663    {
1664      presentMainWindow (data);
1665    }
1666  else
1667    {
1668      g_error ("Unhandled action: %s", action_name);
1669    }
1670
1671  if (changed)
1672    update_model_soon (data);
1673}
Note: See TracBrowser for help on using the repository browser.