source: trunk/gtk/main.c @ 13952

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

(gtk) #5271 'once we start shutting down the application, stop updating the Action states': fixed

  • Property svn:keywords set to Date Rev Author Id
File size: 49.3 KB
Line 
1/******************************************************************************
2 * $Id: main.c 13952 2013-02-04 16:11:08Z 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 (!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->is_closing && !data->refresh_actions_tag)
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  cbdata->is_closing = true;
923
924  /* stop the update timer */
925  if (cbdata->timer)
926    {
927      g_source_remove (cbdata->timer);
928      cbdata->timer = 0;
929    }
930
931  /* stop the refresh-actions timer */
932  if (cbdata->refresh_actions_tag)
933    {
934      g_source_remove (cbdata->refresh_actions_tag);
935      cbdata->refresh_actions_tag = 0;
936    }
937
938  c = GTK_WIDGET (cbdata->wind);
939  gtk_container_remove (GTK_CONTAINER (c), gtk_bin_get_child (GTK_BIN (c)));
940
941  r = gtk_alignment_new (0.5, 0.5, 0.01, 0.01);
942  gtk_container_add (GTK_CONTAINER (c), r);
943
944  p = gtk_grid_new ();
945  gtk_grid_set_column_spacing (GTK_GRID (p), GUI_PAD_BIG);
946  gtk_container_add (GTK_CONTAINER (r), p);
947
948  w = gtk_image_new_from_stock (GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG);
949  gtk_grid_attach (GTK_GRID (p), w, 0, 0, 1, 2);
950
951  w = gtk_label_new (NULL);
952  gtk_label_set_markup (GTK_LABEL (w), _("<b>Closing Connections</b>"));
953  gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
954  gtk_grid_attach (GTK_GRID (p), w, 1, 0, 1, 1);
955
956  w = gtk_label_new (_("Sending upload/download totals to tracker
"));
957  gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
958  gtk_grid_attach (GTK_GRID (p), w, 1, 1, 1, 1);
959
960  b = gtk_alignment_new (0.0, 1.0, 0.01, 0.01);
961  w = gtk_button_new_with_mnemonic (_("_Quit Now"));
962  g_signal_connect (w, "clicked", G_CALLBACK (exit_now_cb), NULL);
963  gtk_container_add (GTK_CONTAINER (b), w);
964  gtk_grid_attach (GTK_GRID (p), b, 1, 2, 1, 1);
965
966  gtk_widget_show_all (r);
967  gtk_widget_grab_focus (w);
968
969  /* clear the UI */
970  gtr_core_clear (cbdata->core);
971
972  /* ensure the window is in its previous position & size.
973   * this seems to be necessary because changing the main window's
974   * child seems to unset the size */
975  gtk_window_resize (cbdata->wind, gtr_pref_int_get (TR_KEY_main_window_width),
976                                   gtr_pref_int_get (TR_KEY_main_window_height));
977  gtk_window_move (cbdata->wind, gtr_pref_int_get (TR_KEY_main_window_x),
978                                 gtr_pref_int_get (TR_KEY_main_window_y));
979
980  /* shut down libT */
981  session_close_data = g_new (struct session_close_struct, 1);
982  session_close_data->cbdata = cbdata;
983  session_close_data->session = gtr_core_close (cbdata->core);
984  g_thread_new ("shutdown-thread", session_close_threadfunc, session_close_data);
985}
986
987static void
988show_torrent_errors (GtkWindow * window, const char * primary, GSList ** files)
989{
990  GSList * l;
991  GtkWidget * w;
992  GString * s = g_string_new (NULL);
993  const char * leader = g_slist_length (*files) > 1
994                      ? gtr_get_unicode_string (GTR_UNICODE_BULLET)
995                      : "";
996
997  for (l=*files; l!=NULL; l=l->next)
998    g_string_append_printf (s, "%s %s\n", leader, (const char*)l->data);
999
1000  w = gtk_message_dialog_new (window,
1001                              GTK_DIALOG_DESTROY_WITH_PARENT,
1002                              GTK_MESSAGE_ERROR,
1003                              GTK_BUTTONS_CLOSE,
1004                              "%s", primary);
1005  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (w),
1006                                            "%s", s->str);
1007  g_signal_connect_swapped (w, "response",
1008                            G_CALLBACK (gtk_widget_destroy), w);
1009  gtk_widget_show (w);
1010  g_string_free (s, TRUE);
1011
1012  g_slist_foreach (*files, (GFunc)g_free, NULL);
1013  g_slist_free (*files);
1014  *files = NULL;
1015}
1016
1017static void
1018flush_torrent_errors (struct cbdata * cbdata)
1019{
1020  if (cbdata->error_list)
1021    show_torrent_errors (cbdata->wind,
1022                         ngettext ("Couldn't add corrupt torrent",
1023                                   "Couldn't add corrupt torrents",
1024                                   g_slist_length (cbdata->error_list)),
1025                         &cbdata->error_list);
1026
1027  if (cbdata->duplicates_list)
1028    show_torrent_errors (cbdata->wind,
1029                         ngettext ("Couldn't add duplicate torrent",
1030                                   "Couldn't add duplicate torrents",
1031                                   g_slist_length (cbdata->duplicates_list)),
1032                         &cbdata->duplicates_list);
1033}
1034
1035static void
1036on_core_error (TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c)
1037{
1038  switch (code)
1039    {
1040      case TR_PARSE_ERR:
1041        c->error_list = g_slist_append (c->error_list, g_path_get_basename (msg));
1042        break;
1043
1044      case TR_PARSE_DUPLICATE:
1045        c->duplicates_list = g_slist_append (c->duplicates_list, g_strdup (msg));
1046        break;
1047
1048      case TR_CORE_ERR_NO_MORE_TORRENTS:
1049        flush_torrent_errors (c);
1050        break;
1051
1052      default:
1053        g_assert_not_reached ();
1054        break;
1055    }
1056}
1057
1058static gboolean
1059on_main_window_focus_in (GtkWidget      * widget UNUSED,
1060                         GdkEventFocus  * event  UNUSED,
1061                         gpointer         gdata)
1062{
1063  struct cbdata * cbdata = gdata;
1064
1065  if (cbdata->wind)
1066    gtk_window_set_urgency_hint (cbdata->wind, FALSE);
1067
1068  return FALSE;
1069}
1070
1071static void
1072on_add_torrent (TrCore * core, tr_ctor * ctor, gpointer gdata)
1073{
1074  struct cbdata * cbdata = gdata;
1075  GtkWidget * w = gtr_torrent_options_dialog_new (cbdata->wind, core, ctor);
1076
1077  g_signal_connect (w, "focus-in-event",
1078                    G_CALLBACK (on_main_window_focus_in),  cbdata);
1079  if (cbdata->wind)
1080    gtk_window_set_urgency_hint (cbdata->wind, TRUE);
1081
1082  gtk_widget_show (w);
1083}
1084
1085static void
1086on_prefs_changed (TrCore * core UNUSED, const tr_quark key, gpointer data)
1087{
1088  struct cbdata * cbdata = data;
1089  tr_session * tr = gtr_core_session (cbdata->core);
1090
1091  switch (key)
1092    {
1093      case TR_KEY_encryption:
1094        tr_sessionSetEncryption (tr, gtr_pref_int_get (key));
1095        break;
1096
1097      case TR_KEY_download_dir:
1098        tr_sessionSetDownloadDir (tr, gtr_pref_string_get (key));
1099        break;
1100
1101      case TR_KEY_message_level:
1102        tr_logSetLevel (gtr_pref_int_get (key));
1103        break;
1104
1105      case TR_KEY_peer_port:
1106        tr_sessionSetPeerPort (tr, gtr_pref_int_get (key));
1107        break;
1108
1109      case TR_KEY_blocklist_enabled:
1110        tr_blocklistSetEnabled (tr, gtr_pref_flag_get (key));
1111        break;
1112
1113      case TR_KEY_blocklist_url:
1114        tr_blocklistSetURL (tr, gtr_pref_string_get (key));
1115        break;
1116
1117      case TR_KEY_show_notification_area_icon:
1118        {
1119          const bool show = gtr_pref_flag_get (key);
1120          if (show && !cbdata->icon)
1121            cbdata->icon = gtr_icon_new (cbdata->core);
1122          else if (!show && cbdata->icon)
1123            g_clear_object (&cbdata->icon);
1124          break;
1125        }
1126
1127      case TR_KEY_speed_limit_down_enabled:
1128        tr_sessionLimitSpeed (tr, TR_DOWN, gtr_pref_flag_get (key));
1129        break;
1130
1131      case TR_KEY_speed_limit_down:
1132        tr_sessionSetSpeedLimit_KBps (tr, TR_DOWN, gtr_pref_int_get (key));
1133        break;
1134
1135      case TR_KEY_speed_limit_up_enabled:
1136        tr_sessionLimitSpeed (tr, TR_UP, gtr_pref_flag_get (key));
1137        break;
1138
1139      case TR_KEY_speed_limit_up:
1140        tr_sessionSetSpeedLimit_KBps (tr, TR_UP, gtr_pref_int_get (key));
1141        break;
1142
1143      case TR_KEY_ratio_limit_enabled:
1144        tr_sessionSetRatioLimited (tr, gtr_pref_flag_get (key));
1145        break;
1146
1147      case TR_KEY_ratio_limit:
1148        tr_sessionSetRatioLimit (tr, gtr_pref_double_get (key));
1149        break;
1150
1151      case TR_KEY_idle_seeding_limit:
1152        tr_sessionSetIdleLimit (tr, gtr_pref_int_get (key));
1153        break;
1154
1155      case TR_KEY_idle_seeding_limit_enabled:
1156        tr_sessionSetIdleLimited (tr, gtr_pref_flag_get (key));
1157        break;
1158
1159      case TR_KEY_port_forwarding_enabled:
1160        tr_sessionSetPortForwardingEnabled (tr, gtr_pref_flag_get (key));
1161        break;
1162
1163      case TR_KEY_pex_enabled:
1164        tr_sessionSetPexEnabled (tr, gtr_pref_flag_get (key));
1165        break;
1166
1167      case TR_KEY_rename_partial_files:
1168        tr_sessionSetIncompleteFileNamingEnabled (tr, gtr_pref_flag_get (key));
1169        break;
1170
1171      case TR_KEY_download_queue_size:
1172        tr_sessionSetQueueSize (tr, TR_DOWN, gtr_pref_int_get (key));
1173        break;
1174
1175      case TR_KEY_queue_stalled_minutes:
1176        tr_sessionSetQueueStalledMinutes (tr, gtr_pref_int_get (key));
1177        break;
1178
1179      case TR_KEY_dht_enabled:
1180        tr_sessionSetDHTEnabled (tr, gtr_pref_flag_get (key));
1181        break;
1182
1183      case TR_KEY_utp_enabled:
1184        tr_sessionSetUTPEnabled (tr, gtr_pref_flag_get (key));
1185        break;
1186
1187      case TR_KEY_lpd_enabled:
1188        tr_sessionSetLPDEnabled (tr, gtr_pref_flag_get (key));
1189        break;
1190
1191      case TR_KEY_rpc_port:
1192        tr_sessionSetRPCPort (tr, gtr_pref_int_get (key));
1193        break;
1194
1195      case TR_KEY_rpc_enabled:
1196        tr_sessionSetRPCEnabled (tr, gtr_pref_flag_get (key));
1197        break;
1198
1199      case TR_KEY_rpc_whitelist:
1200        tr_sessionSetRPCWhitelist (tr, gtr_pref_string_get (key));
1201        break;
1202
1203      case TR_KEY_rpc_whitelist_enabled:
1204        tr_sessionSetRPCWhitelistEnabled (tr, gtr_pref_flag_get (key));
1205        break;
1206
1207      case TR_KEY_rpc_username:
1208        tr_sessionSetRPCUsername (tr, gtr_pref_string_get (key));
1209        break;
1210
1211      case TR_KEY_rpc_password:
1212        tr_sessionSetRPCPassword (tr, gtr_pref_string_get (key));
1213        break;
1214
1215      case TR_KEY_rpc_authentication_required:
1216        tr_sessionSetRPCPasswordEnabled (tr, gtr_pref_flag_get (key));
1217        break;
1218
1219      case TR_KEY_alt_speed_up:
1220        tr_sessionSetAltSpeed_KBps (tr, TR_UP, gtr_pref_int_get (key));
1221        break;
1222
1223      case TR_KEY_alt_speed_down:
1224        tr_sessionSetAltSpeed_KBps (tr, TR_DOWN, gtr_pref_int_get (key));
1225        break;
1226
1227      case TR_KEY_alt_speed_enabled:
1228        {
1229          const bool b = gtr_pref_flag_get (key);
1230          tr_sessionUseAltSpeed (tr, b);
1231          gtr_action_set_toggled (tr_quark_get_string(key,NULL), b);
1232          break;
1233        }
1234
1235      case TR_KEY_alt_speed_time_begin:
1236        tr_sessionSetAltSpeedBegin (tr, gtr_pref_int_get (key));
1237        break;
1238
1239      case TR_KEY_alt_speed_time_end:
1240        tr_sessionSetAltSpeedEnd (tr, gtr_pref_int_get (key));
1241        break;
1242
1243      case TR_KEY_alt_speed_time_enabled:
1244        tr_sessionUseAltSpeedTime (tr, gtr_pref_flag_get (key));
1245        break;
1246
1247      case TR_KEY_alt_speed_time_day:
1248        tr_sessionSetAltSpeedDay (tr, gtr_pref_int_get (key));
1249        break;
1250
1251      case TR_KEY_peer_port_random_on_start:
1252        tr_sessionSetPeerPortRandomOnStart (tr, gtr_pref_flag_get (key));
1253        break;
1254
1255      case TR_KEY_incomplete_dir:
1256        tr_sessionSetIncompleteDir (tr, gtr_pref_string_get (key));
1257        break;
1258
1259      case TR_KEY_incomplete_dir_enabled:
1260        tr_sessionSetIncompleteDirEnabled (tr, gtr_pref_flag_get (key));
1261        break;
1262
1263      case TR_KEY_script_torrent_done_enabled:
1264        tr_sessionSetTorrentDoneScriptEnabled (tr, gtr_pref_flag_get (key));
1265        break;
1266
1267      case TR_KEY_script_torrent_done_filename:
1268        tr_sessionSetTorrentDoneScript (tr, gtr_pref_string_get (key));
1269        break;
1270
1271      case TR_KEY_start_added_torrents:
1272        tr_sessionSetPaused (tr, !gtr_pref_flag_get (key));
1273        break;
1274
1275      case TR_KEY_trash_original_torrent_files:
1276        tr_sessionSetDeleteSource (tr, gtr_pref_flag_get (key));
1277        break;
1278
1279      default:
1280        break;
1281    }
1282}
1283
1284static gboolean
1285update_model_once (gpointer gdata)
1286{
1287  struct cbdata *data = gdata;
1288
1289  /* update the torrent data in the model */
1290  gtr_core_update (data->core);
1291
1292  /* refresh the main window's statusbar and toolbar buttons */
1293  if (data->wind != NULL)
1294    gtr_window_refresh (data->wind);
1295
1296  /* update the actions */
1297  refresh_actions (data);
1298
1299  /* update the status tray icon */
1300  if (data->icon != NULL)
1301    gtr_icon_refresh (data->icon);
1302
1303  data->update_model_soon_tag = 0;
1304  return G_SOURCE_REMOVE;
1305}
1306
1307static void
1308update_model_soon (gpointer gdata)
1309{
1310  struct cbdata *data = gdata;
1311
1312  if (data->update_model_soon_tag == 0)
1313    data->update_model_soon_tag = gdk_threads_add_idle (update_model_once, data);
1314}
1315
1316static gboolean
1317update_model_loop (gpointer gdata)
1318{
1319  const gboolean done = global_sigcount;
1320
1321  if (!done)
1322    update_model_once (gdata);
1323
1324  return !done;
1325}
1326
1327static void
1328show_about_dialog (GtkWindow * parent)
1329{
1330  const char * uri = "http://www.transmissionbt.com/";
1331  const char * authors[] = { "Jordan Lee (Backend; GTK+)",
1332                             "Mitchell Livingston (Backend; OS X)",
1333                             NULL };
1334
1335  gtk_show_about_dialog (parent,
1336    "authors", authors,
1337    "comments", _("A fast and easy BitTorrent client"),
1338    "copyright", _("Copyright (c) The Transmission Project"),
1339    "logo-icon-name", MY_CONFIG_NAME,
1340    "name", g_get_application_name (),
1341    /* Translators: translate "translator-credits" as your name
1342       to have it appear in the credits in the "About"
1343       dialog */
1344    "translator-credits", _("translator-credits"),
1345    "version", LONG_VERSION_STRING,
1346    "website", uri,
1347    "website-label", uri,
1348#ifdef SHOW_LICENSE
1349    "license", LICENSE,
1350    "wrap-license", TRUE,
1351#endif
1352    NULL);
1353}
1354
1355static void
1356append_id_to_benc_list (GtkTreeModel * m, GtkTreePath * path UNUSED,
1357                        GtkTreeIter * iter, gpointer list)
1358{
1359  tr_torrent * tor = NULL;
1360  gtk_tree_model_get (m, iter, MC_TORRENT, &tor, -1);
1361  tr_variantListAddInt (list, tr_torrentId (tor));
1362}
1363
1364static gboolean
1365call_rpc_for_selected_torrents (struct cbdata * data, const char * method)
1366{
1367  tr_variant top, *args, *ids;
1368  gboolean invoked = FALSE;
1369  GtkTreeSelection * s = data->sel;
1370  tr_session * session = gtr_core_session (data->core);
1371
1372  tr_variantInitDict (&top, 2);
1373  tr_variantDictAddStr (&top, TR_KEY_method, method);
1374  args = tr_variantDictAddDict (&top, TR_KEY_arguments, 1);
1375  ids = tr_variantDictAddList (args, TR_KEY_ids, 0);
1376  gtk_tree_selection_selected_foreach (s, append_id_to_benc_list, ids);
1377
1378  if (tr_variantListSize (ids) != 0)
1379    {
1380      int json_len;
1381      char * json = tr_variantToStr (&top, TR_VARIANT_FMT_JSON_LEAN, &json_len);
1382      tr_rpc_request_exec_json (session, json, json_len, NULL, NULL);
1383      g_free (json);
1384      invoked = TRUE;
1385    }
1386
1387  tr_variantFree (&top);
1388  return invoked;
1389}
1390
1391static void
1392open_folder_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED,
1393                     GtkTreeIter * iter, gpointer core)
1394{
1395  int id;
1396  gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1);
1397  gtr_core_open_folder (core, id);
1398}
1399
1400static gboolean
1401on_message_window_closed (void)
1402{
1403  gtr_action_set_toggled ("toggle-message-log", FALSE);
1404  return FALSE;
1405}
1406
1407static void
1408accumulate_selected_torrents (GtkTreeModel  * model, GtkTreePath   * path UNUSED,
1409                              GtkTreeIter   * iter, gpointer        gdata)
1410{
1411  int id;
1412  GSList ** data = gdata;
1413
1414  gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1);
1415  *data = g_slist_append (*data, GINT_TO_POINTER (id));
1416}
1417
1418static void
1419remove_selected (struct cbdata * data, gboolean delete_files)
1420{
1421  GSList * l = NULL;
1422
1423  gtk_tree_selection_selected_foreach (data->sel, accumulate_selected_torrents, &l);
1424
1425  if (l != NULL)
1426    gtr_confirm_remove (data->wind, data->core, l, delete_files);
1427}
1428
1429static void
1430start_all_torrents (struct cbdata * data)
1431{
1432  tr_session * session = gtr_core_session (data->core);
1433  const char * cmd = "{ \"method\": \"torrent-start\" }";
1434  tr_rpc_request_exec_json (session, cmd, strlen (cmd), NULL, NULL);
1435}
1436
1437static void
1438pause_all_torrents (struct cbdata * data)
1439{
1440  tr_session * session = gtr_core_session (data->core);
1441  const char * cmd = "{ \"method\": \"torrent-stop\" }";
1442  tr_rpc_request_exec_json (session, cmd, strlen (cmd), NULL, NULL);
1443}
1444
1445static tr_torrent*
1446get_first_selected_torrent (struct cbdata * data)
1447{
1448  tr_torrent * tor = NULL;
1449  GtkTreeModel * m;
1450  GList * l = gtk_tree_selection_get_selected_rows (data->sel, &m);
1451  if (l != NULL)
1452    {
1453      GtkTreePath * p = l->data;
1454      GtkTreeIter i;
1455      if (gtk_tree_model_get_iter (m, &i, p))
1456        gtk_tree_model_get (m, &i, MC_TORRENT, &tor, -1);
1457    }
1458  g_list_foreach (l, (GFunc)gtk_tree_path_free, NULL);
1459  g_list_free (l);
1460  return tor;
1461}
1462
1463static void
1464copy_magnet_link_to_clipboard (GtkWidget * w, tr_torrent * tor)
1465{
1466  char * magnet = tr_torrentGetMagnetLink (tor);
1467  GdkDisplay * display = gtk_widget_get_display (w);
1468  GdkAtom selection;
1469  GtkClipboard * clipboard;
1470
1471  /* this is The Right Thing for copy/paste... */
1472  selection = GDK_SELECTION_CLIPBOARD;
1473  clipboard = gtk_clipboard_get_for_display (display, selection);
1474  gtk_clipboard_set_text (clipboard, magnet, -1);
1475
1476  /* ...but people using plain ol' X need this instead */
1477  selection = GDK_SELECTION_PRIMARY;
1478  clipboard = gtk_clipboard_get_for_display (display, selection);
1479  gtk_clipboard_set_text (clipboard, magnet, -1);
1480
1481  /* cleanup */
1482  tr_free (magnet);
1483}
1484
1485void
1486gtr_actions_handler (const char * action_name, gpointer user_data)
1487{
1488  gboolean changed = FALSE;
1489  struct cbdata * data = user_data;
1490
1491  if (!strcmp (action_name, "open-torrent-from-url"))
1492    {
1493      GtkWidget * w = gtr_torrent_open_from_url_dialog_new (data->wind, data->core);
1494      gtk_widget_show (w);
1495    }
1496  else if (!strcmp (action_name, "open-torrent-menu")
1497        || !strcmp (action_name, "open-torrent-toolbar"))
1498    {
1499      GtkWidget * w = gtr_torrent_open_from_file_dialog_new (data->wind, data->core);
1500      gtk_widget_show (w);
1501    }
1502    else if (!strcmp (action_name, "show-stats"))
1503    {
1504        GtkWidget * dialog = gtr_stats_dialog_new (data->wind, data->core);
1505        gtk_widget_show (dialog);
1506    }
1507  else if (!strcmp (action_name, "donate"))
1508    {
1509      gtr_open_uri ("http://www.transmissionbt.com/donate.php");
1510    }
1511  else if (!strcmp (action_name, "pause-all-torrents"))
1512    {
1513      pause_all_torrents (data);
1514    }
1515  else if (!strcmp (action_name, "start-all-torrents"))
1516    {
1517      start_all_torrents (data);
1518    }
1519  else if (!strcmp (action_name, "copy-magnet-link-to-clipboard"))
1520    {
1521      tr_torrent * tor = get_first_selected_torrent (data);
1522      if (tor != NULL)
1523        {
1524          copy_magnet_link_to_clipboard (GTK_WIDGET (data->wind), tor);
1525        }
1526    }
1527  else if (!strcmp (action_name, "relocate-torrent"))
1528    {
1529      GSList * ids = get_selected_torrent_ids (data);
1530      if (ids != NULL)
1531        {
1532          GtkWindow * parent = data->wind;
1533          GtkWidget * w = gtr_relocate_dialog_new (parent, data->core, ids);
1534          gtk_widget_show (w);
1535        }
1536    }
1537  else if (!strcmp (action_name, "torrent-start")
1538        || !strcmp (action_name, "torrent-start-now")
1539        || !strcmp (action_name, "torrent-stop")
1540        || !strcmp (action_name, "torrent-reannounce")
1541        || !strcmp (action_name, "torrent-verify")
1542        || !strcmp (action_name, "queue-move-top")
1543        || !strcmp (action_name, "queue-move-up")
1544        || !strcmp (action_name, "queue-move-down")
1545        || !strcmp (action_name, "queue-move-bottom"))
1546    {
1547      changed |= call_rpc_for_selected_torrents (data, action_name);
1548    }
1549  else if (!strcmp (action_name, "open-torrent-folder"))
1550    {
1551      gtk_tree_selection_selected_foreach (data->sel, open_folder_foreach, data->core);
1552    }
1553  else if (!strcmp (action_name, "show-torrent-properties"))
1554    {
1555      show_details_dialog_for_selected_torrents (data);
1556    }
1557  else if (!strcmp (action_name, "new-torrent"))
1558    {
1559      GtkWidget * w = gtr_torrent_creation_dialog_new (data->wind, data->core);
1560      gtk_widget_show (w);
1561    }
1562  else if (!strcmp (action_name, "remove-torrent"))
1563    {
1564      remove_selected (data, FALSE);
1565    }
1566  else if (!strcmp (action_name, "delete-torrent"))
1567    {
1568      remove_selected (data, TRUE);
1569    }
1570  else if (!strcmp (action_name, "quit"))
1571    {
1572      on_app_exit (data);
1573    }
1574  else if (!strcmp (action_name, "select-all"))
1575    {
1576      gtk_tree_selection_select_all (data->sel);
1577    }
1578  else if (!strcmp (action_name, "deselect-all"))
1579    {
1580      gtk_tree_selection_unselect_all (data->sel);
1581    }
1582  else if (!strcmp (action_name, "edit-preferences"))
1583    {
1584      if (NULL == data->prefs)
1585        {
1586          data->prefs = gtr_prefs_dialog_new (data->wind, G_OBJECT (data->core));
1587          g_signal_connect (data->prefs, "destroy",
1588                            G_CALLBACK (gtk_widget_destroyed), &data->prefs);
1589        }
1590        gtr_window_present (GTK_WINDOW (data->prefs));
1591    }
1592  else if (!strcmp (action_name, "toggle-message-log"))
1593    {
1594      if (!data->msgwin)
1595        {
1596          GtkWidget * win = gtr_message_log_window_new (data->wind, data->core);
1597          g_signal_connect (win, "destroy", G_CALLBACK (on_message_window_closed), NULL);
1598          data->msgwin = win;
1599        }
1600      else
1601        {
1602          gtr_action_set_toggled ("toggle-message-log", FALSE);
1603          gtk_widget_destroy (data->msgwin);
1604          data->msgwin = NULL;
1605        }
1606    }
1607  else if (!strcmp (action_name, "show-about-dialog"))
1608    {
1609      show_about_dialog (data->wind);
1610    }
1611  else if (!strcmp (action_name, "help"))
1612    {
1613      gtr_open_uri (gtr_get_help_uri ());
1614    }
1615  else if (!strcmp (action_name, "toggle-main-window"))
1616    {
1617      toggleMainWindow (data);
1618    }
1619  else if (!strcmp (action_name, "present-main-window"))
1620    {
1621      presentMainWindow (data);
1622    }
1623  else
1624    {
1625      g_error ("Unhandled action: %s", action_name);
1626    }
1627
1628  if (changed)
1629    update_model_soon (data);
1630}
Note: See TracBrowser for help on using the repository browser.