1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-26 01:27:28 +00:00
transmission/gtk/main.c
2015-01-02 11:15:31 +00:00

1667 lines
50 KiB
C

/******************************************************************************
* $Id$
*
* Copyright (c) Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#include <locale.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h> /* exit () */
#include <time.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <libtransmission/transmission.h>
#include <libtransmission/rpcimpl.h>
#include <libtransmission/utils.h>
#include <libtransmission/version.h>
#include "actions.h"
#include "conf.h"
#include "details.h"
#include "dialogs.h"
#include "hig.h"
#include "makemeta-ui.h"
#include "msgwin.h"
#include "notify.h"
#include "open-dialog.h"
#include "relocate.h"
#include "stats.h"
#include "tr-core.h"
#include "tr-icon.h"
#include "tr-prefs.h"
#include "tr-window.h"
#include "util.h"
#define MY_CONFIG_NAME "transmission"
#define MY_READABLE_NAME "transmission-gtk"
#define TR_RESOURCE_PATH "/com/transmissionbt/transmission/"
#define SHOW_LICENSE
static const char * LICENSE =
"Copyright 2005-2014. All code is copyrighted by the respective authors.\n"
"\n"
"Transmission can be redistributed and/or modified under the terms of the "
"GNU GPL versions 2 or 3 or by any future license endorsed by Mnemosyne LLC.\n"
"\n"
"In addition, linking to and/or using OpenSSL is allowed.\n"
"\n"
"This program is distributed in the hope that it will be useful, "
"but WITHOUT ANY WARRANTY; without even the implied warranty of "
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
"\n"
"Some of Transmission's source files have more permissive licenses. "
"Those files may, of course, be used on their own under their own terms.\n";
struct cbdata
{
char * config_dir;
gboolean start_paused;
gboolean is_iconified;
gboolean is_closing;
guint activation_count;
guint timer;
guint update_model_soon_tag;
guint refresh_actions_tag;
gpointer icon;
GtkWindow * wind;
TrCore * core;
GtkWidget * msgwin;
GtkWidget * prefs;
GSList * error_list;
GSList * duplicates_list;
GSList * details;
GtkTreeSelection * sel;
};
static void
gtr_window_present (GtkWindow * window)
{
gtk_window_present_with_time (window, gtk_get_current_event_time ());
}
/***
****
**** DETAILS DIALOGS MANAGEMENT
****
***/
static int
compare_integers (gconstpointer a, gconstpointer b)
{
return GPOINTER_TO_INT (a) - GPOINTER_TO_INT (b);
}
static char*
get_details_dialog_key (GSList * id_list)
{
GSList * l;
GSList * tmp = g_slist_sort (g_slist_copy (id_list), compare_integers);
GString * gstr = g_string_new (NULL);
for (l=tmp; l!=NULL; l=l->next)
g_string_append_printf (gstr, "%d ", GPOINTER_TO_INT (l->data));
g_slist_free (tmp);
return g_string_free (gstr, FALSE);
}
static void
get_selected_torrent_ids_foreach (GtkTreeModel * model,
GtkTreePath * p UNUSED,
GtkTreeIter * iter,
gpointer gdata)
{
int id;
GSList ** ids = gdata;
gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1);
*ids = g_slist_append (*ids, GINT_TO_POINTER (id));
}
static GSList*
get_selected_torrent_ids (struct cbdata * data)
{
GSList * ids = NULL;
gtk_tree_selection_selected_foreach (data->sel,
get_selected_torrent_ids_foreach,
&ids);
return ids;
}
static void
on_details_dialog_closed (gpointer gdata, GObject * dead)
{
struct cbdata * data = gdata;
data->details = g_slist_remove (data->details, dead);
}
static void
show_details_dialog_for_selected_torrents (struct cbdata * data)
{
GtkWidget * dialog = NULL;
GSList * l;
GSList * ids = get_selected_torrent_ids (data);
char * key = get_details_dialog_key (ids);
for (l=data->details; dialog==NULL && l!=NULL; l=l->next)
if (!g_strcmp0 (key, g_object_get_data (l->data, "key")))
dialog = l->data;
if (dialog == NULL)
{
dialog = gtr_torrent_details_dialog_new (GTK_WINDOW (data->wind), data->core);
gtr_torrent_details_dialog_set_torrents (dialog, ids);
g_object_set_data_full (G_OBJECT (dialog), "key", g_strdup (key), g_free);
g_object_weak_ref (G_OBJECT (dialog), on_details_dialog_closed, data);
data->details = g_slist_append (data->details, dialog);
gtk_widget_show (dialog);
}
gtr_window_present (GTK_WINDOW (dialog));
g_free (key);
g_slist_free (ids);
}
/****
*****
***** ON SELECTION CHANGED
*****
****/
struct counts_data
{
int total_count;
int queued_count;
int stopped_count;
};
static void
get_selected_torrent_counts_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED,
GtkTreeIter * iter, gpointer user_data)
{
int activity = 0;
struct counts_data * counts = user_data;
++counts->total_count;
gtk_tree_model_get (model, iter, MC_ACTIVITY, &activity, -1);
if ((activity == TR_STATUS_DOWNLOAD_WAIT) || (activity == TR_STATUS_SEED_WAIT))
++counts->queued_count;
if (activity == TR_STATUS_STOPPED)
++counts->stopped_count;
}
static void
get_selected_torrent_counts (struct cbdata * data, struct counts_data * counts)
{
counts->total_count = 0;
counts->queued_count = 0;
counts->stopped_count = 0;
gtk_tree_selection_selected_foreach (data->sel, get_selected_torrent_counts_foreach, counts);
}
static void
count_updatable_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED,
GtkTreeIter * iter, gpointer accumulated_status)
{
tr_torrent * tor;
gtk_tree_model_get (model, iter, MC_TORRENT, &tor, -1);
*(int*)accumulated_status |= tr_torrentCanManualUpdate (tor);
}
static gboolean
refresh_actions (gpointer gdata)
{
struct cbdata * data = gdata;
if (!data->is_closing)
{
int canUpdate;
struct counts_data sel_counts;
const size_t total = gtr_core_get_torrent_count (data->core);
const size_t active = gtr_core_get_active_torrent_count (data->core);
const int torrent_count = gtk_tree_model_iter_n_children (gtr_core_model (data->core), NULL);
bool has_selection;
get_selected_torrent_counts (data, &sel_counts);
has_selection = sel_counts.total_count > 0;
gtr_action_set_sensitive ("select-all", torrent_count != 0);
gtr_action_set_sensitive ("deselect-all", torrent_count != 0);
gtr_action_set_sensitive ("pause-all-torrents", active != 0);
gtr_action_set_sensitive ("start-all-torrents", active != total);
gtr_action_set_sensitive ("torrent-stop", (sel_counts.stopped_count < sel_counts.total_count));
gtr_action_set_sensitive ("torrent-start", (sel_counts.stopped_count) > 0);
gtr_action_set_sensitive ("torrent-start-now", (sel_counts.stopped_count + sel_counts.queued_count) > 0);
gtr_action_set_sensitive ("torrent-verify", has_selection);
gtr_action_set_sensitive ("remove-torrent", has_selection);
gtr_action_set_sensitive ("delete-torrent", has_selection);
gtr_action_set_sensitive ("relocate-torrent", has_selection);
gtr_action_set_sensitive ("queue-move-top", has_selection);
gtr_action_set_sensitive ("queue-move-up", has_selection);
gtr_action_set_sensitive ("queue-move-down", has_selection);
gtr_action_set_sensitive ("queue-move-bottom", has_selection);
gtr_action_set_sensitive ("show-torrent-properties", has_selection);
gtr_action_set_sensitive ("open-torrent-folder", sel_counts.total_count == 1);
gtr_action_set_sensitive ("copy-magnet-link-to-clipboard", sel_counts.total_count == 1);
canUpdate = 0;
gtk_tree_selection_selected_foreach (data->sel, count_updatable_foreach, &canUpdate);
gtr_action_set_sensitive ("torrent-reannounce", canUpdate != 0);
}
data->refresh_actions_tag = 0;
return G_SOURCE_REMOVE;
}
static void
refresh_actions_soon (gpointer gdata)
{
struct cbdata * data = gdata;
if (!data->is_closing && !data->refresh_actions_tag)
data->refresh_actions_tag = gdk_threads_add_idle (refresh_actions, data);
}
static void
on_selection_changed (GtkTreeSelection * s UNUSED, gpointer gdata)
{
refresh_actions_soon (gdata);
}
/***
****
***/
static gboolean
has_magnet_link_handler (void)
{
GAppInfo * app_info = g_app_info_get_default_for_uri_scheme ("magnet");
const gboolean has_handler = app_info != NULL;
g_clear_object (&app_info);
return has_handler;
}
static void
register_magnet_link_handler (void)
{
GError * error;
GAppInfo * app;
const char * const content_type = "x-scheme-handler/magnet";
error = NULL;
app = g_app_info_create_from_commandline ("transmission-gtk",
"transmission-gtk",
G_APP_INFO_CREATE_SUPPORTS_URIS,
&error);
g_app_info_set_as_default_for_type (app, content_type, &error);
if (error != NULL)
{
g_warning (_("Error registering Transmission as a %s handler: %s"),
content_type,
error->message);
g_error_free (error);
}
g_clear_object (&app);
}
static void
ensure_magnet_handler_exists (void)
{
if (!has_magnet_link_handler ())
register_magnet_link_handler ();
}
static void
on_main_window_size_allocated (GtkWidget * gtk_window,
GtkAllocation * alloc UNUSED,
gpointer gdata UNUSED)
{
GdkWindow * gdk_window = gtk_widget_get_window (gtk_window);
const gboolean isMaximized = (gdk_window != NULL)
&& (gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_MAXIMIZED);
gtr_pref_int_set (TR_KEY_main_window_is_maximized, isMaximized);
if (!isMaximized)
{
int x, y, w, h;
gtk_window_get_position (GTK_WINDOW (gtk_window), &x, &y);
gtk_window_get_size (GTK_WINDOW (gtk_window), &w, &h);
gtr_pref_int_set (TR_KEY_main_window_x, x);
gtr_pref_int_set (TR_KEY_main_window_y, y);
gtr_pref_int_set (TR_KEY_main_window_width, w);
gtr_pref_int_set (TR_KEY_main_window_height, h);
}
}
/***
**** listen to changes that come from RPC
***/
struct on_rpc_changed_struct
{
TrCore * core;
tr_rpc_callback_type type;
int torrent_id;
};
static gboolean
on_rpc_changed_idle (gpointer gdata)
{
tr_torrent * tor;
struct on_rpc_changed_struct * data = gdata;
switch (data->type)
{
case TR_RPC_SESSION_CLOSE:
gtr_action_activate ("quit");
break;
case TR_RPC_TORRENT_ADDED:
if ((tor = gtr_core_find_torrent (data->core, data->torrent_id)))
gtr_core_add_torrent (data->core, tor, true);
break;
case TR_RPC_TORRENT_REMOVING:
gtr_core_remove_torrent (data->core, data->torrent_id, false);
break;
case TR_RPC_TORRENT_TRASHING:
gtr_core_remove_torrent (data->core, data->torrent_id, true);
break;
case TR_RPC_SESSION_CHANGED: {
int i;
tr_variant tmp;
tr_variant * newval;
tr_variant * oldvals = gtr_pref_get_all ();
tr_quark key;
GSList * l;
GSList * changed_keys = NULL;
tr_session * session = gtr_core_session (data->core);
tr_variantInitDict (&tmp, 100);
tr_sessionGetSettings (session, &tmp);
for (i=0; tr_variantDictChild (&tmp, i, &key, &newval); ++i)
{
bool changed;
tr_variant * oldval = tr_variantDictFind (oldvals, key);
if (!oldval)
{
changed = true;
}
else
{
char * a = tr_variantToStr (oldval, TR_VARIANT_FMT_BENC, NULL);
char * b = tr_variantToStr (newval, TR_VARIANT_FMT_BENC, NULL);
changed = g_strcmp0 (a, b) != 0;
tr_free (b);
tr_free (a);
}
if (changed)
changed_keys = g_slist_append (changed_keys, GINT_TO_POINTER(key));
}
tr_sessionGetSettings (session, oldvals);
for (l=changed_keys; l!=NULL; l=l->next)
gtr_core_pref_changed (data->core, GPOINTER_TO_INT(l->data));
g_slist_free (changed_keys);
tr_variantFree (&tmp);
break;
}
case TR_RPC_TORRENT_CHANGED:
case TR_RPC_TORRENT_MOVED:
case TR_RPC_TORRENT_STARTED:
case TR_RPC_TORRENT_STOPPED:
case TR_RPC_SESSION_QUEUE_POSITIONS_CHANGED:
/* nothing interesting to do here */
break;
}
g_free (data);
return G_SOURCE_REMOVE;
}
static tr_rpc_callback_status
on_rpc_changed (tr_session * session G_GNUC_UNUSED,
tr_rpc_callback_type type,
struct tr_torrent * tor,
void * gdata)
{
struct cbdata * cbdata = gdata;
struct on_rpc_changed_struct * data;
data = g_new (struct on_rpc_changed_struct, 1);
data->core = cbdata->core;
data->type = type;
data->torrent_id = tr_torrentId (tor);
gdk_threads_add_idle (on_rpc_changed_idle, data);
return TR_RPC_NOREMOVE;
}
/***
**** signal handling
***/
static sig_atomic_t global_sigcount = 0;
static struct cbdata * sighandler_cbdata = NULL;
static void
signal_handler (int sig)
{
if (++global_sigcount > 1)
{
signal (sig, SIG_DFL);
raise (sig);
}
else if ((sig == SIGINT) || (sig == SIGTERM))
{
g_message (_("Got signal %d; trying to shut down cleanly. Do it again if it gets stuck."), sig);
gtr_actions_handler ("quit", sighandler_cbdata);
}
}
/****
*****
*****
****/
static void app_setup (GtkWindow * wind, struct cbdata * cbdata);
static void
on_startup (GApplication * application, gpointer user_data)
{
GError * error;
const char * str;
GtkWindow * win;
GtkUIManager * ui_manager;
tr_session * session;
struct cbdata * cbdata = user_data;
signal (SIGINT, signal_handler);
signal (SIGTERM, signal_handler);
sighandler_cbdata = cbdata;
/* ensure the directories are created */
if ((str = gtr_pref_string_get (TR_KEY_download_dir)))
g_mkdir_with_parents (str, 0777);
if ((str = gtr_pref_string_get (TR_KEY_incomplete_dir)))
g_mkdir_with_parents (str, 0777);
/* initialize the libtransmission session */
session = tr_sessionInit ("gtk", cbdata->config_dir, TRUE, gtr_pref_get_all ());
gtr_pref_flag_set (TR_KEY_alt_speed_enabled, tr_sessionUsesAltSpeed (session));
gtr_pref_int_set (TR_KEY_peer_port, tr_sessionGetPeerPort (session));
cbdata->core = gtr_core_new (session);
/* init the ui manager */
error = NULL;
ui_manager = gtk_ui_manager_new ();
gtr_actions_init (ui_manager, cbdata);
gtk_ui_manager_add_ui_from_resource (ui_manager, TR_RESOURCE_PATH "transmission-ui.xml", &error);
g_assert_no_error (error);
gtk_ui_manager_ensure_update (ui_manager);
/* create main window now to be a parent to any error dialogs */
win = GTK_WINDOW (gtr_window_new (GTK_APPLICATION (application), ui_manager, cbdata->core));
g_signal_connect (win, "size-allocate", G_CALLBACK (on_main_window_size_allocated), cbdata);
g_application_hold (application);
g_object_weak_ref (G_OBJECT (win), (GWeakNotify)g_application_release, application);
app_setup (win, cbdata);
tr_sessionSetRPCCallback (session, on_rpc_changed, cbdata);
/* check & see if it's time to update the blocklist */
if (gtr_pref_flag_get (TR_KEY_blocklist_enabled))
{
if (gtr_pref_flag_get (TR_KEY_blocklist_updates_enabled))
{
const int64_t last_time = gtr_pref_int_get (TR_KEY_blocklist_date);
const int SECONDS_IN_A_WEEK = 7 * 24 * 60 * 60;
const time_t now = time (NULL);
if (last_time + SECONDS_IN_A_WEEK < now)
gtr_core_blocklist_update (cbdata->core);
}
}
/* if there's no magnet link handler registered, register us */
ensure_magnet_handler_exists ();
}
static void
on_activate (GApplication * app UNUSED, struct cbdata * cbdata)
{
cbdata->activation_count++;
/* GApplication emits an 'activate' signal when bootstrapping the primary.
* Ordinarily we handle that by presenting the main window, but if the user
* user started Transmission minimized, ignore that initial signal... */
if (cbdata->is_iconified && (cbdata->activation_count == 1))
return;
gtr_action_activate ("present-main-window");
}
static void
open_files (GSList * files, gpointer gdata)
{
struct cbdata * cbdata = gdata;
const gboolean do_start = gtr_pref_flag_get (TR_KEY_start_added_torrents) && !cbdata->start_paused;
const gboolean do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window);
const gboolean do_notify = TRUE;
gtr_core_add_files (cbdata->core, files, do_start, do_prompt, do_notify);
}
static void
on_open (GApplication * application UNUSED,
GFile ** f,
gint file_count,
gchar * hint UNUSED,
gpointer gdata)
{
int i;
GSList * files = NULL;
for (i=0; i<file_count; i++)
files = g_slist_prepend (files, f[i]);
open_files (files, gdata);
g_slist_free (files);
}
/***
****
***/
int
main (int argc, char ** argv)
{
int ret;
struct stat sb;
char * application_id;
GtkApplication * app;
GOptionContext * option_context;
bool show_version = false;
GError * error = NULL;
struct cbdata cbdata;
GOptionEntry option_entries[] = {
{ "config-dir", 'g', 0, G_OPTION_ARG_FILENAME, &cbdata.config_dir, _("Where to look for configuration files"), NULL },
{ "paused", 'p', 0, G_OPTION_ARG_NONE, &cbdata.start_paused, _("Start with all torrents paused"), NULL },
{ "minimized", 'm', 0, G_OPTION_ARG_NONE, &cbdata.is_iconified, _("Start minimized in notification area"), NULL },
{ "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, _("Show version number and exit"), NULL },
{ NULL, 0, 0, 0, NULL, NULL, NULL }
};
/* default settings */
memset (&cbdata, 0, sizeof (struct cbdata));
cbdata.config_dir = (char*) tr_getDefaultConfigDir (MY_CONFIG_NAME);
/* init i18n */
setlocale (LC_ALL, "");
bindtextdomain (MY_READABLE_NAME, TRANSMISSIONLOCALEDIR);
bind_textdomain_codeset (MY_READABLE_NAME, "UTF-8");
textdomain (MY_READABLE_NAME);
/* init glib/gtk */
#if !GLIB_CHECK_VERSION(2,35,4)
g_type_init ();
#endif
gtk_init (&argc, &argv);
g_set_application_name (_("Transmission"));
gtk_window_set_default_icon_name (MY_CONFIG_NAME);
/* parse the command line */
option_context = g_option_context_new (_("[torrent files or urls]"));
g_option_context_add_main_entries (option_context, option_entries, GETTEXT_PACKAGE);
g_option_context_set_translation_domain (option_context, GETTEXT_PACKAGE);
if (!g_option_context_parse (option_context, &argc, &argv, &error))
{
g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"), error->message, argv[0]);
g_error_free (error);
g_option_context_free (option_context);
return 1;
}
g_option_context_free (option_context);
/* handle the trivial "version" option */
if (show_version)
{
fprintf (stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING);
return 0;
}
/* init the unit formatters */
tr_formatter_mem_init (mem_K, _ (mem_K_str), _ (mem_M_str), _ (mem_G_str), _ (mem_T_str));
tr_formatter_size_init (disk_K, _ (disk_K_str), _ (disk_M_str), _ (disk_G_str), _ (disk_T_str));
tr_formatter_speed_init (speed_K, _ (speed_K_str), _ (speed_M_str), _ (speed_G_str), _ (speed_T_str));
/* set up the config dir */
gtr_pref_init (cbdata.config_dir);
g_mkdir_with_parents (cbdata.config_dir, 0755);
/* init notifications */
gtr_notify_init ();
/* init the application for the specified config dir */
stat (cbdata.config_dir, &sb);
application_id = g_strdup_printf ("com.transmissionbt.transmission_%lu_%lu", (unsigned long)sb.st_dev, (unsigned long)sb.st_ino);
app = gtk_application_new (application_id, G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "open", G_CALLBACK (on_open), &cbdata);
g_signal_connect (app, "startup", G_CALLBACK (on_startup), &cbdata);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), &cbdata);
ret = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
g_free (application_id);
return ret;
}
static void
on_core_busy (TrCore * core UNUSED, gboolean busy, struct cbdata * c)
{
gtr_window_set_busy (c->wind, busy);
}
static void on_core_error (TrCore *, guint, const char *, struct cbdata *);
static void on_add_torrent (TrCore *, tr_ctor *, gpointer);
static void on_prefs_changed (TrCore * core, const tr_quark key, gpointer);
static void main_window_setup (struct cbdata * cbdata, GtkWindow * wind);
static gboolean update_model_loop (gpointer gdata);
static gboolean update_model_once (gpointer gdata);
static void
app_setup (GtkWindow * wind, struct cbdata * cbdata)
{
if (cbdata->is_iconified)
gtr_pref_flag_set (TR_KEY_show_notification_area_icon, TRUE);
gtr_actions_set_core (cbdata->core);
/* set up core handlers */
g_signal_connect (cbdata->core, "busy", G_CALLBACK (on_core_busy), cbdata);
g_signal_connect (cbdata->core, "add-error", G_CALLBACK (on_core_error), cbdata);
g_signal_connect (cbdata->core, "add-prompt", G_CALLBACK (on_add_torrent), cbdata);
g_signal_connect (cbdata->core, "prefs-changed", G_CALLBACK (on_prefs_changed), cbdata);
/* add torrents from command-line and saved state */
gtr_core_load (cbdata->core, cbdata->start_paused);
gtr_core_torrents_added (cbdata->core);
/* set up main window */
main_window_setup (cbdata, wind);
/* set up the icon */
on_prefs_changed (cbdata->core, TR_KEY_show_notification_area_icon, cbdata);
/* start model update timer */
cbdata->timer = gdk_threads_add_timeout_seconds (MAIN_WINDOW_REFRESH_INTERVAL_SECONDS, update_model_loop, cbdata);
update_model_once (cbdata);
/* either show the window or iconify it */
if (!cbdata->is_iconified)
{
gtk_widget_show (GTK_WIDGET (wind));
}
else
{
gtk_window_set_skip_taskbar_hint (cbdata->wind,
cbdata->icon != NULL);
cbdata->is_iconified = FALSE; // ensure that the next toggle iconifies
gtr_action_set_toggled ("toggle-main-window", FALSE);
}
if (!gtr_pref_flag_get (TR_KEY_user_has_given_informed_consent))
{
GtkWidget * w = gtk_message_dialog_new (GTK_WINDOW (wind),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_OTHER,
GTK_BUTTONS_NONE,
"%s",
_("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."));
gtk_dialog_add_button (GTK_DIALOG (w), GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT);
gtk_dialog_add_button (GTK_DIALOG (w), _("I _Agree"), GTK_RESPONSE_ACCEPT);
gtk_dialog_set_default_response (GTK_DIALOG (w), GTK_RESPONSE_ACCEPT);
switch (gtk_dialog_run (GTK_DIALOG (w)))
{
case GTK_RESPONSE_ACCEPT:
/* only show it once */
gtr_pref_flag_set (TR_KEY_user_has_given_informed_consent, TRUE);
gtk_widget_destroy (w);
break;
default:
exit (0);
}
}
}
static void
presentMainWindow (struct cbdata * cbdata)
{
GtkWindow * window = cbdata->wind;
if (cbdata->is_iconified)
{
cbdata->is_iconified = false;
gtk_window_set_skip_taskbar_hint (window, FALSE);
}
if (!gtk_widget_get_visible (GTK_WIDGET (window)))
{
gtk_window_resize (window, gtr_pref_int_get (TR_KEY_main_window_width),
gtr_pref_int_get (TR_KEY_main_window_height));
gtk_window_move (window, gtr_pref_int_get (TR_KEY_main_window_x),
gtr_pref_int_get (TR_KEY_main_window_y));
gtr_widget_set_visible (GTK_WIDGET (window), TRUE);
}
gtr_window_present (window);
gdk_window_raise (gtk_widget_get_window (GTK_WIDGET(window)));
}
static void
hideMainWindow (struct cbdata * cbdata)
{
GtkWindow * window = cbdata->wind;
gtk_window_set_skip_taskbar_hint (window, TRUE);
gtr_widget_set_visible (GTK_WIDGET (window), FALSE);
cbdata->is_iconified = true;
}
static void
toggleMainWindow (struct cbdata * cbdata)
{
if (cbdata->is_iconified)
presentMainWindow (cbdata);
else
hideMainWindow (cbdata);
}
static void on_app_exit (gpointer vdata);
static gboolean
winclose (GtkWidget * w UNUSED,
GdkEvent * event UNUSED,
gpointer gdata)
{
struct cbdata * cbdata = gdata;
if (cbdata->icon != NULL)
gtr_action_activate ("toggle-main-window");
else
on_app_exit (cbdata);
return TRUE; /* don't propagate event further */
}
static void
rowChangedCB (GtkTreeModel * model UNUSED,
GtkTreePath * path,
GtkTreeIter * iter UNUSED,
gpointer gdata)
{
struct cbdata * data = gdata;
if (gtk_tree_selection_path_is_selected (data->sel, path))
refresh_actions_soon (data);
}
static void
on_drag_data_received (GtkWidget * widget UNUSED,
GdkDragContext * drag_context,
gint x UNUSED,
gint y UNUSED,
GtkSelectionData * selection_data,
guint info UNUSED,
guint time_,
gpointer gdata)
{
guint i;
char ** uris = gtk_selection_data_get_uris (selection_data);
const guint file_count = g_strv_length (uris);
GSList * files = NULL;
for (i=0; i<file_count; ++i)
files = g_slist_prepend (files, g_file_new_for_uri (uris[i]));
open_files (files, gdata);
/* cleanup */
g_slist_foreach (files, (GFunc)g_object_unref, NULL);
g_slist_free (files);
g_strfreev (uris);
gtk_drag_finish (drag_context, true, FALSE, time_);
}
static void
main_window_setup (struct cbdata * cbdata, GtkWindow * wind)
{
GtkWidget * w;
GtkTreeModel * model;
GtkTreeSelection * sel;
g_assert (NULL == cbdata->wind);
cbdata->wind = wind;
cbdata->sel = sel = GTK_TREE_SELECTION (gtr_window_get_selection (cbdata->wind));
g_signal_connect (sel, "changed", G_CALLBACK (on_selection_changed), cbdata);
on_selection_changed (sel, cbdata);
model = gtr_core_model (cbdata->core);
g_signal_connect (model, "row-changed", G_CALLBACK (rowChangedCB), cbdata);
g_signal_connect (wind, "delete-event", G_CALLBACK (winclose), cbdata);
refresh_actions (cbdata);
/* register to handle URIs that get dragged onto our main window */
w = GTK_WIDGET (wind);
gtk_drag_dest_set (w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
gtk_drag_dest_add_uri_targets (w);
g_signal_connect (w, "drag-data-received", G_CALLBACK (on_drag_data_received), cbdata);
}
static gboolean
on_session_closed (gpointer gdata)
{
GSList * tmp;
struct cbdata * cbdata = gdata;
tmp = g_slist_copy (cbdata->details);
g_slist_foreach (tmp, (GFunc)gtk_widget_destroy, NULL);
g_slist_free (tmp);
if (cbdata->prefs)
gtk_widget_destroy (GTK_WIDGET (cbdata->prefs));
if (cbdata->wind)
gtk_widget_destroy (GTK_WIDGET (cbdata->wind));
g_object_unref (cbdata->core);
if (cbdata->icon)
g_object_unref (cbdata->icon);
g_slist_foreach (cbdata->error_list, (GFunc)g_free, NULL);
g_slist_free (cbdata->error_list);
g_slist_foreach (cbdata->duplicates_list, (GFunc)g_free, NULL);
g_slist_free (cbdata->duplicates_list);
return G_SOURCE_REMOVE;
}
struct session_close_struct
{
tr_session * session;
struct cbdata * cbdata;
};
/* since tr_sessionClose () is a blocking function,
* delegate its call to another thread here... when it's done,
* punt the GUI teardown back to the GTK+ thread */
static gpointer
session_close_threadfunc (gpointer gdata)
{
struct session_close_struct * data = gdata;
tr_sessionClose (data->session);
gdk_threads_add_idle (on_session_closed, data->cbdata);
g_free (data);
return NULL;
}
static void
exit_now_cb (GtkWidget *w UNUSED, gpointer data UNUSED)
{
exit (0);
}
static void
on_app_exit (gpointer vdata)
{
GtkWidget *r, *p, *b, *w, *c;
struct cbdata * cbdata = vdata;
struct session_close_struct * session_close_data;
if (cbdata->is_closing)
return;
cbdata->is_closing = true;
/* stop the update timer */
if (cbdata->timer)
{
g_source_remove (cbdata->timer);
cbdata->timer = 0;
}
/* stop the refresh-actions timer */
if (cbdata->refresh_actions_tag)
{
g_source_remove (cbdata->refresh_actions_tag);
cbdata->refresh_actions_tag = 0;
}
c = GTK_WIDGET (cbdata->wind);
gtk_container_remove (GTK_CONTAINER (c), gtk_bin_get_child (GTK_BIN (c)));
r = gtk_alignment_new (0.5, 0.5, 0.01, 0.01);
gtk_container_add (GTK_CONTAINER (c), r);
p = gtk_grid_new ();
gtk_grid_set_column_spacing (GTK_GRID (p), GUI_PAD_BIG);
gtk_container_add (GTK_CONTAINER (r), p);
w = gtk_image_new_from_stock (GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG);
gtk_grid_attach (GTK_GRID (p), w, 0, 0, 1, 2);
w = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (w), _("<b>Closing Connections</b>"));
gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
gtk_grid_attach (GTK_GRID (p), w, 1, 0, 1, 1);
w = gtk_label_new (_("Sending upload/download totals to tracker…"));
gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
gtk_grid_attach (GTK_GRID (p), w, 1, 1, 1, 1);
b = gtk_alignment_new (0.0, 1.0, 0.01, 0.01);
w = gtk_button_new_with_mnemonic (_("_Quit Now"));
g_signal_connect (w, "clicked", G_CALLBACK (exit_now_cb), NULL);
gtk_container_add (GTK_CONTAINER (b), w);
gtk_grid_attach (GTK_GRID (p), b, 1, 2, 1, 1);
gtk_widget_show_all (r);
gtk_widget_grab_focus (w);
/* clear the UI */
gtr_core_clear (cbdata->core);
/* ensure the window is in its previous position & size.
* this seems to be necessary because changing the main window's
* child seems to unset the size */
gtk_window_resize (cbdata->wind, gtr_pref_int_get (TR_KEY_main_window_width),
gtr_pref_int_get (TR_KEY_main_window_height));
gtk_window_move (cbdata->wind, gtr_pref_int_get (TR_KEY_main_window_x),
gtr_pref_int_get (TR_KEY_main_window_y));
/* shut down libT */
session_close_data = g_new (struct session_close_struct, 1);
session_close_data->cbdata = cbdata;
session_close_data->session = gtr_core_close (cbdata->core);
g_thread_new ("shutdown-thread", session_close_threadfunc, session_close_data);
}
static void
show_torrent_errors (GtkWindow * window, const char * primary, GSList ** files)
{
GSList * l;
GtkWidget * w;
GString * s = g_string_new (NULL);
const char * leader = g_slist_length (*files) > 1
? gtr_get_unicode_string (GTR_UNICODE_BULLET)
: "";
for (l=*files; l!=NULL; l=l->next)
g_string_append_printf (s, "%s %s\n", leader, (const char*)l->data);
w = gtk_message_dialog_new (window,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"%s", primary);
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (w),
"%s", s->str);
g_signal_connect_swapped (w, "response",
G_CALLBACK (gtk_widget_destroy), w);
gtk_widget_show (w);
g_string_free (s, TRUE);
g_slist_foreach (*files, (GFunc)g_free, NULL);
g_slist_free (*files);
*files = NULL;
}
static void
flush_torrent_errors (struct cbdata * cbdata)
{
if (cbdata->error_list)
show_torrent_errors (cbdata->wind,
ngettext ("Couldn't add corrupt torrent",
"Couldn't add corrupt torrents",
g_slist_length (cbdata->error_list)),
&cbdata->error_list);
if (cbdata->duplicates_list)
show_torrent_errors (cbdata->wind,
ngettext ("Couldn't add duplicate torrent",
"Couldn't add duplicate torrents",
g_slist_length (cbdata->duplicates_list)),
&cbdata->duplicates_list);
}
static void
on_core_error (TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c)
{
switch (code)
{
case TR_PARSE_ERR:
c->error_list = g_slist_append (c->error_list, g_path_get_basename (msg));
break;
case TR_PARSE_DUPLICATE:
c->duplicates_list = g_slist_append (c->duplicates_list, g_strdup (msg));
break;
case TR_CORE_ERR_NO_MORE_TORRENTS:
flush_torrent_errors (c);
break;
default:
g_assert_not_reached ();
break;
}
}
static gboolean
on_main_window_focus_in (GtkWidget * widget UNUSED,
GdkEventFocus * event UNUSED,
gpointer gdata)
{
struct cbdata * cbdata = gdata;
if (cbdata->wind)
gtk_window_set_urgency_hint (cbdata->wind, FALSE);
return FALSE;
}
static void
on_add_torrent (TrCore * core, tr_ctor * ctor, gpointer gdata)
{
struct cbdata * cbdata = gdata;
GtkWidget * w = gtr_torrent_options_dialog_new (cbdata->wind, core, ctor);
g_signal_connect (w, "focus-in-event",
G_CALLBACK (on_main_window_focus_in), cbdata);
if (cbdata->wind)
gtk_window_set_urgency_hint (cbdata->wind, TRUE);
gtk_widget_show (w);
}
static void
on_prefs_changed (TrCore * core UNUSED, const tr_quark key, gpointer data)
{
struct cbdata * cbdata = data;
tr_session * tr = gtr_core_session (cbdata->core);
switch (key)
{
case TR_KEY_encryption:
tr_sessionSetEncryption (tr, gtr_pref_int_get (key));
break;
case TR_KEY_download_dir:
tr_sessionSetDownloadDir (tr, gtr_pref_string_get (key));
break;
case TR_KEY_message_level:
tr_logSetLevel (gtr_pref_int_get (key));
break;
case TR_KEY_peer_port:
tr_sessionSetPeerPort (tr, gtr_pref_int_get (key));
break;
case TR_KEY_blocklist_enabled:
tr_blocklistSetEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_blocklist_url:
tr_blocklistSetURL (tr, gtr_pref_string_get (key));
break;
case TR_KEY_show_notification_area_icon:
{
const bool show = gtr_pref_flag_get (key);
if (show && !cbdata->icon)
cbdata->icon = gtr_icon_new (cbdata->core);
else if (!show && cbdata->icon)
g_clear_object (&cbdata->icon);
break;
}
case TR_KEY_speed_limit_down_enabled:
tr_sessionLimitSpeed (tr, TR_DOWN, gtr_pref_flag_get (key));
break;
case TR_KEY_speed_limit_down:
tr_sessionSetSpeedLimit_KBps (tr, TR_DOWN, gtr_pref_int_get (key));
break;
case TR_KEY_speed_limit_up_enabled:
tr_sessionLimitSpeed (tr, TR_UP, gtr_pref_flag_get (key));
break;
case TR_KEY_speed_limit_up:
tr_sessionSetSpeedLimit_KBps (tr, TR_UP, gtr_pref_int_get (key));
break;
case TR_KEY_ratio_limit_enabled:
tr_sessionSetRatioLimited (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_ratio_limit:
tr_sessionSetRatioLimit (tr, gtr_pref_double_get (key));
break;
case TR_KEY_idle_seeding_limit:
tr_sessionSetIdleLimit (tr, gtr_pref_int_get (key));
break;
case TR_KEY_idle_seeding_limit_enabled:
tr_sessionSetIdleLimited (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_port_forwarding_enabled:
tr_sessionSetPortForwardingEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_pex_enabled:
tr_sessionSetPexEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_rename_partial_files:
tr_sessionSetIncompleteFileNamingEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_download_queue_size:
tr_sessionSetQueueSize (tr, TR_DOWN, gtr_pref_int_get (key));
break;
case TR_KEY_queue_stalled_minutes:
tr_sessionSetQueueStalledMinutes (tr, gtr_pref_int_get (key));
break;
case TR_KEY_dht_enabled:
tr_sessionSetDHTEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_utp_enabled:
tr_sessionSetUTPEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_lpd_enabled:
tr_sessionSetLPDEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_rpc_port:
tr_sessionSetRPCPort (tr, gtr_pref_int_get (key));
break;
case TR_KEY_rpc_enabled:
tr_sessionSetRPCEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_rpc_whitelist:
tr_sessionSetRPCWhitelist (tr, gtr_pref_string_get (key));
break;
case TR_KEY_rpc_whitelist_enabled:
tr_sessionSetRPCWhitelistEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_rpc_username:
tr_sessionSetRPCUsername (tr, gtr_pref_string_get (key));
break;
case TR_KEY_rpc_password:
tr_sessionSetRPCPassword (tr, gtr_pref_string_get (key));
break;
case TR_KEY_rpc_authentication_required:
tr_sessionSetRPCPasswordEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_alt_speed_up:
tr_sessionSetAltSpeed_KBps (tr, TR_UP, gtr_pref_int_get (key));
break;
case TR_KEY_alt_speed_down:
tr_sessionSetAltSpeed_KBps (tr, TR_DOWN, gtr_pref_int_get (key));
break;
case TR_KEY_alt_speed_enabled:
{
const bool b = gtr_pref_flag_get (key);
tr_sessionUseAltSpeed (tr, b);
gtr_action_set_toggled (tr_quark_get_string(key,NULL), b);
break;
}
case TR_KEY_alt_speed_time_begin:
tr_sessionSetAltSpeedBegin (tr, gtr_pref_int_get (key));
break;
case TR_KEY_alt_speed_time_end:
tr_sessionSetAltSpeedEnd (tr, gtr_pref_int_get (key));
break;
case TR_KEY_alt_speed_time_enabled:
tr_sessionUseAltSpeedTime (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_alt_speed_time_day:
tr_sessionSetAltSpeedDay (tr, gtr_pref_int_get (key));
break;
case TR_KEY_peer_port_random_on_start:
tr_sessionSetPeerPortRandomOnStart (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_incomplete_dir:
tr_sessionSetIncompleteDir (tr, gtr_pref_string_get (key));
break;
case TR_KEY_incomplete_dir_enabled:
tr_sessionSetIncompleteDirEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_script_torrent_done_enabled:
tr_sessionSetTorrentDoneScriptEnabled (tr, gtr_pref_flag_get (key));
break;
case TR_KEY_script_torrent_done_filename:
tr_sessionSetTorrentDoneScript (tr, gtr_pref_string_get (key));
break;
case TR_KEY_start_added_torrents:
tr_sessionSetPaused (tr, !gtr_pref_flag_get (key));
break;
case TR_KEY_trash_original_torrent_files:
tr_sessionSetDeleteSource (tr, gtr_pref_flag_get (key));
break;
default:
break;
}
}
static gboolean
update_model_once (gpointer gdata)
{
struct cbdata *data = gdata;
/* update the torrent data in the model */
gtr_core_update (data->core);
/* refresh the main window's statusbar and toolbar buttons */
if (data->wind != NULL)
gtr_window_refresh (data->wind);
/* update the actions */
refresh_actions (data);
/* update the status tray icon */
if (data->icon != NULL)
gtr_icon_refresh (data->icon);
data->update_model_soon_tag = 0;
return G_SOURCE_REMOVE;
}
static void
update_model_soon (gpointer gdata)
{
struct cbdata *data = gdata;
if (data->update_model_soon_tag == 0)
data->update_model_soon_tag = gdk_threads_add_idle (update_model_once, data);
}
static gboolean
update_model_loop (gpointer gdata)
{
const gboolean done = global_sigcount;
if (!done)
update_model_once (gdata);
return !done;
}
static void
show_about_dialog (GtkWindow * parent)
{
const char * uri = "http://www.transmissionbt.com/";
const char * authors[] = { "Jordan Lee (Backend; GTK+)",
"Mitchell Livingston (Backend; OS X)",
NULL };
gtk_show_about_dialog (parent,
"authors", authors,
"comments", _("A fast and easy BitTorrent client"),
"copyright", _("Copyright (c) The Transmission Project"),
"logo-icon-name", MY_CONFIG_NAME,
"name", g_get_application_name (),
/* Translators: translate "translator-credits" as your name
to have it appear in the credits in the "About"
dialog */
"translator-credits", _("translator-credits"),
"version", LONG_VERSION_STRING,
"website", uri,
"website-label", uri,
#ifdef SHOW_LICENSE
"license", LICENSE,
"wrap-license", TRUE,
#endif
NULL);
}
static void
append_id_to_benc_list (GtkTreeModel * m, GtkTreePath * path UNUSED,
GtkTreeIter * iter, gpointer list)
{
tr_torrent * tor = NULL;
gtk_tree_model_get (m, iter, MC_TORRENT, &tor, -1);
tr_variantListAddInt (list, tr_torrentId (tor));
}
static gboolean
call_rpc_for_selected_torrents (struct cbdata * data, const char * method)
{
tr_variant top, *args, *ids;
gboolean invoked = FALSE;
GtkTreeSelection * s = data->sel;
tr_session * session = gtr_core_session (data->core);
tr_variantInitDict (&top, 2);
tr_variantDictAddStr (&top, TR_KEY_method, method);
args = tr_variantDictAddDict (&top, TR_KEY_arguments, 1);
ids = tr_variantDictAddList (args, TR_KEY_ids, 0);
gtk_tree_selection_selected_foreach (s, append_id_to_benc_list, ids);
if (tr_variantListSize (ids) != 0)
{
int json_len;
char * json = tr_variantToStr (&top, TR_VARIANT_FMT_JSON_LEAN, &json_len);
tr_rpc_request_exec_json (session, json, json_len, NULL, NULL);
g_free (json);
invoked = TRUE;
}
tr_variantFree (&top);
return invoked;
}
static void
open_folder_foreach (GtkTreeModel * model, GtkTreePath * path UNUSED,
GtkTreeIter * iter, gpointer core)
{
int id;
gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1);
gtr_core_open_folder (core, id);
}
static gboolean
on_message_window_closed (void)
{
gtr_action_set_toggled ("toggle-message-log", FALSE);
return FALSE;
}
static void
accumulate_selected_torrents (GtkTreeModel * model, GtkTreePath * path UNUSED,
GtkTreeIter * iter, gpointer gdata)
{
int id;
GSList ** data = gdata;
gtk_tree_model_get (model, iter, MC_TORRENT_ID, &id, -1);
*data = g_slist_append (*data, GINT_TO_POINTER (id));
}
static void
remove_selected (struct cbdata * data, gboolean delete_files)
{
GSList * l = NULL;
gtk_tree_selection_selected_foreach (data->sel, accumulate_selected_torrents, &l);
if (l != NULL)
gtr_confirm_remove (data->wind, data->core, l, delete_files);
}
static void
start_all_torrents (struct cbdata * data)
{
tr_session * session = gtr_core_session (data->core);
const char * cmd = "{ \"method\": \"torrent-start\" }";
tr_rpc_request_exec_json (session, cmd, strlen (cmd), NULL, NULL);
}
static void
pause_all_torrents (struct cbdata * data)
{
tr_session * session = gtr_core_session (data->core);
const char * cmd = "{ \"method\": \"torrent-stop\" }";
tr_rpc_request_exec_json (session, cmd, strlen (cmd), NULL, NULL);
}
static tr_torrent*
get_first_selected_torrent (struct cbdata * data)
{
tr_torrent * tor = NULL;
GtkTreeModel * m;
GList * l = gtk_tree_selection_get_selected_rows (data->sel, &m);
if (l != NULL)
{
GtkTreePath * p = l->data;
GtkTreeIter i;
if (gtk_tree_model_get_iter (m, &i, p))
gtk_tree_model_get (m, &i, MC_TORRENT, &tor, -1);
}
g_list_foreach (l, (GFunc)gtk_tree_path_free, NULL);
g_list_free (l);
return tor;
}
static void
copy_magnet_link_to_clipboard (GtkWidget * w, tr_torrent * tor)
{
char * magnet = tr_torrentGetMagnetLink (tor);
GdkDisplay * display = gtk_widget_get_display (w);
GdkAtom selection;
GtkClipboard * clipboard;
/* this is The Right Thing for copy/paste... */
selection = GDK_SELECTION_CLIPBOARD;
clipboard = gtk_clipboard_get_for_display (display, selection);
gtk_clipboard_set_text (clipboard, magnet, -1);
/* ...but people using plain ol' X need this instead */
selection = GDK_SELECTION_PRIMARY;
clipboard = gtk_clipboard_get_for_display (display, selection);
gtk_clipboard_set_text (clipboard, magnet, -1);
/* cleanup */
tr_free (magnet);
}
void
gtr_actions_handler (const char * action_name, gpointer user_data)
{
gboolean changed = FALSE;
struct cbdata * data = user_data;
if (!g_strcmp0 (action_name, "open-torrent-from-url"))
{
GtkWidget * w = gtr_torrent_open_from_url_dialog_new (data->wind, data->core);
gtk_widget_show (w);
}
else if (!g_strcmp0 (action_name, "open-torrent-menu")
|| !g_strcmp0 (action_name, "open-torrent-toolbar"))
{
GtkWidget * w = gtr_torrent_open_from_file_dialog_new (data->wind, data->core);
gtk_widget_show (w);
}
else if (!g_strcmp0 (action_name, "show-stats"))
{
GtkWidget * dialog = gtr_stats_dialog_new (data->wind, data->core);
gtk_widget_show (dialog);
}
else if (!g_strcmp0 (action_name, "donate"))
{
gtr_open_uri ("http://www.transmissionbt.com/donate.php");
}
else if (!g_strcmp0 (action_name, "pause-all-torrents"))
{
pause_all_torrents (data);
}
else if (!g_strcmp0 (action_name, "start-all-torrents"))
{
start_all_torrents (data);
}
else if (!g_strcmp0 (action_name, "copy-magnet-link-to-clipboard"))
{
tr_torrent * tor = get_first_selected_torrent (data);
if (tor != NULL)
{
copy_magnet_link_to_clipboard (GTK_WIDGET (data->wind), tor);
}
}
else if (!g_strcmp0 (action_name, "relocate-torrent"))
{
GSList * ids = get_selected_torrent_ids (data);
if (ids != NULL)
{
GtkWindow * parent = data->wind;
GtkWidget * w = gtr_relocate_dialog_new (parent, data->core, ids);
gtk_widget_show (w);
}
}
else if (!g_strcmp0 (action_name, "torrent-start")
|| !g_strcmp0 (action_name, "torrent-start-now")
|| !g_strcmp0 (action_name, "torrent-stop")
|| !g_strcmp0 (action_name, "torrent-reannounce")
|| !g_strcmp0 (action_name, "torrent-verify")
|| !g_strcmp0 (action_name, "queue-move-top")
|| !g_strcmp0 (action_name, "queue-move-up")
|| !g_strcmp0 (action_name, "queue-move-down")
|| !g_strcmp0 (action_name, "queue-move-bottom"))
{
changed |= call_rpc_for_selected_torrents (data, action_name);
}
else if (!g_strcmp0 (action_name, "open-torrent-folder"))
{
gtk_tree_selection_selected_foreach (data->sel, open_folder_foreach, data->core);
}
else if (!g_strcmp0 (action_name, "show-torrent-properties"))
{
show_details_dialog_for_selected_torrents (data);
}
else if (!g_strcmp0 (action_name, "new-torrent"))
{
GtkWidget * w = gtr_torrent_creation_dialog_new (data->wind, data->core);
gtk_widget_show (w);
}
else if (!g_strcmp0 (action_name, "remove-torrent"))
{
remove_selected (data, FALSE);
}
else if (!g_strcmp0 (action_name, "delete-torrent"))
{
remove_selected (data, TRUE);
}
else if (!g_strcmp0 (action_name, "quit"))
{
on_app_exit (data);
}
else if (!g_strcmp0 (action_name, "select-all"))
{
gtk_tree_selection_select_all (data->sel);
}
else if (!g_strcmp0 (action_name, "deselect-all"))
{
gtk_tree_selection_unselect_all (data->sel);
}
else if (!g_strcmp0 (action_name, "edit-preferences"))
{
if (NULL == data->prefs)
{
data->prefs = gtr_prefs_dialog_new (data->wind, G_OBJECT (data->core));
g_signal_connect (data->prefs, "destroy",
G_CALLBACK (gtk_widget_destroyed), &data->prefs);
}
gtr_window_present (GTK_WINDOW (data->prefs));
}
else if (!g_strcmp0 (action_name, "toggle-message-log"))
{
if (!data->msgwin)
{
GtkWidget * win = gtr_message_log_window_new (data->wind, data->core);
g_signal_connect (win, "destroy", G_CALLBACK (on_message_window_closed), NULL);
data->msgwin = win;
}
else
{
gtr_action_set_toggled ("toggle-message-log", FALSE);
gtk_widget_destroy (data->msgwin);
data->msgwin = NULL;
}
}
else if (!g_strcmp0 (action_name, "show-about-dialog"))
{
show_about_dialog (data->wind);
}
else if (!g_strcmp0 (action_name, "help"))
{
gtr_open_uri (gtr_get_help_uri ());
}
else if (!g_strcmp0 (action_name, "toggle-main-window"))
{
toggleMainWindow (data);
}
else if (!g_strcmp0 (action_name, "present-main-window"))
{
presentMainWindow (data);
}
else
{
g_error ("Unhandled action: %s", action_name);
}
if (changed)
update_model_soon (data);
}