source: trunk/gtk/main.c @ 13868

Last change on this file since 13868 was 13868, checked in by jordan, 8 years ago

make all the log functions/structs/enums use a single 'tr_log' namespace, such as tr_logGetQueue, tr_logAddInfo, tr_logIsLevelActive

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