source: trunk/gtk/main.c @ 14018

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

(gtk) use replace strcmp() with g_strcmp0() everywhere

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