1
/******************************************************************************
2
* $Id: main.c 11368 2010-10-31 17:16:12Z charles $
4
* Copyright (c) 2005-2008 Transmission authors and contributors
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:
13
* The above copyright notice and this permission notice shall be included in
14
* all copies or substantial portions of the Software.
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
*****************************************************************************/
26
#include <sys/param.h>
35
#include <glib/gi18n.h>
36
#include <glib/gstdio.h>
39
#include <launchpad-integration.h>
42
#include <libtransmission/transmission.h>
43
#include <libtransmission/rpcimpl.h>
44
#include <libtransmission/utils.h>
45
#include <libtransmission/version.h>
48
#include "add-dialog.h"
53
#include "makemeta-ui.h"
61
#include "tr-torrent.h"
62
#include "tr-window.h"
66
#define MY_CONFIG_NAME "transmission"
67
#define MY_READABLE_NAME "transmission-gtk"
69
#if GTK_CHECK_VERSION( 2, 8, 0 )
71
static const char * LICENSE =
72
"The OS X client, CLI client, and parts of libtransmission are licensed under the terms of the MIT license.\n\n"
73
"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"
74
"1. The MIT-licensed portions of Transmission listed above are exempt from GPLv2 clause 2(b) and may retain their MIT license.\n\n"
75
"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.";
91
GtkTreeSelection * sel;
92
GtkWidget * quit_dialog;
100
compareInts( const void * a, const void * b )
102
return *(int*)a - *(int*)b;
106
getDetailsDialogKey( GSList * id_list )
112
GString * gstr = g_string_new( NULL );
114
n = g_slist_length( id_list );
115
ids = g_new( int, n );
117
for( l=id_list; l!=NULL; l=l->next )
118
ids[i++] = GPOINTER_TO_INT( l->data );
120
qsort( ids, n, sizeof(int), compareInts );
123
g_string_append_printf( gstr, "%d ", ids[i] );
126
return g_string_free( gstr, FALSE );
129
struct DetailsDialogHandle
136
getSelectedTorrentIds( struct cbdata * data )
138
GtkTreeSelection * s;
139
GtkTreeModel * model;
141
GList * paths = NULL;
144
/* build a list of the selected torrents' ids */
145
s = tr_window_get_selection( data->wind );
146
for( paths=l=gtk_tree_selection_get_selected_rows(s,&model); l; l=l->next ) {
148
if( gtk_tree_model_get_iter( model, &iter, l->data ) ) {
150
gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
151
ids = g_slist_append( ids, GINT_TO_POINTER( tr_torrentId( tor ) ) );
156
g_list_foreach( paths, (GFunc)gtk_tree_path_free, NULL );
157
g_list_free( paths );
161
static struct DetailsDialogHandle*
162
findDetailsDialogFromIds( struct cbdata * cbdata, GSList * ids )
165
struct DetailsDialogHandle * ret = NULL;
166
char * key = getDetailsDialogKey( ids );
168
for( l=cbdata->details; l!=NULL && ret==NULL; l=l->next ) {
169
struct DetailsDialogHandle * h = l->data;
170
if( !strcmp( h->key, key ) )
178
static struct DetailsDialogHandle*
179
findDetailsDialogFromWidget( struct cbdata * cbdata, gpointer w )
182
struct DetailsDialogHandle * ret = NULL;
184
for( l=cbdata->details; l!=NULL && ret==NULL; l=l->next ) {
185
struct DetailsDialogHandle * h = l->data;
197
static void appsetup( TrWindow * wind,
201
gboolean minimized );
203
static void winsetup( struct cbdata * cbdata,
206
static void wannaquit( gpointer vdata );
208
static void coreerr( TrCore *, guint, const char *, struct cbdata * );
210
static void onAddTorrent( TrCore *,
214
static void prefschanged( TrCore * core,
218
static gboolean updatemodel( gpointer gdata );
225
#include <gconf/gconf.h>
226
#include <gconf/gconf-client.h>
230
registerMagnetLinkHandler( void )
235
GConfClient * client = gconf_client_get_default( );
236
const char * key = "/desktop/gnome/url-handlers/magnet/command";
238
/* if there's already a manget handler registered, don't do anything */
239
value = gconf_client_get( client, key, NULL );
242
gconf_value_free( value );
247
if( !gconf_client_set_string( client, key, "transmission '%s'", &err ) )
249
tr_inf( "Unable to register Transmission as default magnet link handler: \"%s\"", err->message );
250
g_clear_error( &err );
254
gconf_client_set_bool( client, "/desktop/gnome/url-handlers/magnet/needs_terminal", FALSE, NULL );
255
gconf_client_set_bool( client, "/desktop/gnome/url-handlers/magnet/enabled", TRUE, NULL );
256
tr_inf( "Transmission registered as default magnet link handler" );
273
accumulateStatusForeach( GtkTreeModel * model,
274
GtkTreePath * path UNUSED,
279
struct counts_data * counts = user_data;
281
++counts->totalCount;
283
gtk_tree_model_get( model, iter, MC_ACTIVITY, &activity, -1 );
285
if( activity == TR_STATUS_STOPPED )
286
++counts->inactiveCount;
288
++counts->activeCount;
292
getTorrentCounts( struct cbdata * data, struct counts_data * counts )
294
counts->activeCount = 0;
295
counts->inactiveCount = 0;
296
counts->totalCount = 0;
298
gtk_tree_selection_selected_foreach( data->sel, accumulateStatusForeach, counts );
302
accumulateCanUpdateForeach( GtkTreeModel * model,
303
GtkTreePath * path UNUSED,
305
gpointer accumulated_status )
308
gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
309
*(int*)accumulated_status |= tr_torrentCanManualUpdate( tor );
313
refreshActions( gpointer gdata )
316
struct counts_data counts;
317
struct cbdata * data = gdata;
319
getTorrentCounts( data, &counts );
320
action_sensitize( "pause-torrent", counts.activeCount != 0 );
321
action_sensitize( "start-torrent", counts.inactiveCount != 0 );
322
action_sensitize( "remove-torrent", counts.totalCount != 0 );
323
action_sensitize( "delete-torrent", counts.totalCount != 0 );
324
action_sensitize( "verify-torrent", counts.totalCount != 0 );
325
action_sensitize( "relocate-torrent", counts.totalCount != 0 );
326
action_sensitize( "show-torrent-properties", counts.totalCount != 0 );
327
action_sensitize( "open-torrent-folder", counts.totalCount == 1 );
328
action_sensitize( "copy-magnet-link-to-clipboard", counts.totalCount == 1 );
331
gtk_tree_selection_selected_foreach( data->sel, accumulateCanUpdateForeach, &canUpdate );
332
action_sensitize( "update-tracker", canUpdate != 0 );
335
GtkTreeView * view = gtk_tree_selection_get_tree_view( data->sel );
336
GtkTreeModel * model = gtk_tree_view_get_model( view );
337
const int torrentCount = gtk_tree_model_iter_n_children( model, NULL ) != 0;
338
action_sensitize( "select-all", torrentCount != 0 );
339
action_sensitize( "deselect-all", torrentCount != 0 );
343
const int total = tr_core_get_torrent_count( data->core );
344
const int active = tr_core_get_active_torrent_count( data->core );
345
action_sensitize( "pause-all-torrents", active != 0 );
346
action_sensitize( "start-all-torrents", active != total );
353
selectionChangedCB( GtkTreeSelection * s UNUSED, gpointer data )
355
gtr_idle_add( refreshActions, data );
359
onMainWindowSizeAllocated( GtkWidget * window,
360
GtkAllocation * alloc UNUSED,
361
gpointer gdata UNUSED )
363
const gboolean isMaximized = window->window
364
&& ( gdk_window_get_state( window->window )
365
& GDK_WINDOW_STATE_MAXIMIZED );
367
pref_int_set( PREF_KEY_MAIN_WINDOW_IS_MAXIMIZED, isMaximized );
372
gtk_window_get_position( GTK_WINDOW( window ), &x, &y );
373
gtk_window_get_size( GTK_WINDOW( window ), &w, &h );
374
pref_int_set( PREF_KEY_MAIN_WINDOW_X, x );
375
pref_int_set( PREF_KEY_MAIN_WINDOW_Y, y );
376
pref_int_set( PREF_KEY_MAIN_WINDOW_WIDTH, w );
377
pref_int_set( PREF_KEY_MAIN_WINDOW_HEIGHT, h );
381
static sig_atomic_t global_sigcount = 0;
382
static struct cbdata * sighandler_cbdata = NULL;
385
signal_handler( int sig )
387
if( ++global_sigcount > 1 )
389
signal( sig, SIG_DFL );
396
g_message( _( "Got signal %d; trying to shut down cleanly. Do it again if it gets stuck." ), sig );
397
doAction( "quit", sighandler_cbdata );
401
g_message( "unhandled signal" );
406
struct remove_torrent_idle_data
413
remove_torrent_idle( gpointer gdata )
415
struct remove_torrent_idle_data * data = gdata;
416
tr_core_remove_torrent_from_id( data->core, data->id, FALSE );
418
return FALSE; /* tell g_idle not to call this func twice */
422
setupsighandlers( void )
424
signal( SIGINT, signal_handler );
425
signal( SIGKILL, signal_handler );
428
static tr_rpc_callback_status
429
onRPCChanged( tr_session * session,
430
tr_rpc_callback_type type,
431
struct tr_torrent * tor,
434
tr_rpc_callback_status status = TR_RPC_OK;
435
struct cbdata * cbdata = gdata;
436
gdk_threads_enter( );
440
case TR_RPC_TORRENT_ADDED:
441
tr_core_add_torrent( cbdata->core, tr_torrent_new_preexisting( tor ), TRUE );
444
case TR_RPC_TORRENT_REMOVING: {
445
struct remove_torrent_idle_data * data = g_new0( struct remove_torrent_idle_data, 1 );
446
data->id = tr_torrentId( tor );
447
data->core = cbdata->core;
448
gtr_idle_add( remove_torrent_idle, data );
449
status = TR_RPC_NOREMOVE;
453
case TR_RPC_SESSION_CHANGED: {
457
tr_benc * oldvals = pref_get_all( );
460
GSList * changed_keys = NULL;
461
tr_bencInitDict( &tmp, 100 );
462
tr_sessionGetSettings( session, &tmp );
463
for( i=0; tr_bencDictChild( &tmp, i, &key, &newval ); ++i )
466
tr_benc * oldval = tr_bencDictFind( oldvals, key );
470
char * a = tr_bencToStr( oldval, TR_FMT_BENC, NULL );
471
char * b = tr_bencToStr( newval, TR_FMT_BENC, NULL );
472
changed = strcmp( a, b ) != 0;
478
changed_keys = g_slist_append( changed_keys, (gpointer)key );
480
tr_sessionGetSettings( session, oldvals );
482
for( l=changed_keys; l!=NULL; l=l->next )
483
prefschanged( cbdata->core, key, cbdata );
485
g_slist_free( changed_keys );
490
case TR_RPC_TORRENT_CHANGED:
491
case TR_RPC_TORRENT_MOVED:
492
case TR_RPC_TORRENT_STARTED:
493
case TR_RPC_TORRENT_STOPPED:
494
/* nothing interesting to do here */
498
gdk_threads_leave( );
503
checkfilenames( int argc, char **argv )
507
char * pwd = g_get_current_dir( );
509
for( i=0; i<argc; ++i )
511
if( gtr_is_supported_url( argv[i] ) || gtr_is_magnet_link( argv[i] ) )
513
ret = g_slist_prepend( ret, g_strdup( argv[i] ) );
515
else /* local file */
517
char * filename = g_path_is_absolute( argv[i] )
518
? g_strdup ( argv[i] )
519
: g_build_filename( pwd, argv[i], NULL );
521
if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
522
ret = g_slist_prepend( ret, filename );
524
if( gtr_is_hex_hashcode( argv[i] ) )
525
ret = g_slist_prepend( ret, g_strdup_printf( "magnet:?xt=urn:btih:%s", argv[i] ) );
532
return g_slist_reverse( ret );
536
main( int argc, char ** argv )
541
gboolean didinit = FALSE;
542
gboolean didlock = FALSE;
543
gboolean showversion = FALSE;
544
gboolean startpaused = FALSE;
545
gboolean startminimized = FALSE;
546
const char * domain = MY_READABLE_NAME;
547
char * configDir = NULL;
548
gtr_lockfile_state_t tr_state;
550
GOptionEntry entries[] = {
551
{ "paused", 'p', 0, G_OPTION_ARG_NONE,
552
&startpaused, _( "Start with all torrents paused" ), NULL },
553
{ "version", '\0', 0, G_OPTION_ARG_NONE,
554
&showversion, _( "Show version number and exit" ), NULL },
555
#ifdef STATUS_ICON_SUPPORTED
556
{ "minimized", 'm', 0, G_OPTION_ARG_NONE,
558
_( "Start minimized in notification area" ), NULL },
560
{ "config-dir", 'g', 0, G_OPTION_ARG_FILENAME, &configDir,
561
_( "Where to look for configuration files" ), NULL },
562
{ NULL, 0, 0, 0, NULL, NULL, NULL }
565
/* bind the gettext domain */
566
setlocale( LC_ALL, "" );
567
bindtextdomain( domain, TRANSMISSIONLOCALEDIR );
568
bind_textdomain_codeset( domain, "UTF-8" );
569
textdomain( domain );
570
g_set_application_name( _( "Transmission" ) );
571
tr_formatter_mem_init( mem_K, _(mem_K_str), _(mem_M_str), _(mem_G_str), _(mem_T_str) );
572
tr_formatter_size_init( disk_K, _(disk_K_str), _(disk_M_str), _(disk_G_str), _(disk_T_str) );
573
tr_formatter_speed_init( speed_K, _(speed_K_str), _(speed_M_str), _(speed_G_str), _(speed_T_str) );
576
if( !g_thread_supported( ) )
577
g_thread_init( NULL );
580
if( !gtk_init_with_args( &argc, &argv, (char*)_( "[torrent files or urls]" ), entries,
581
(char*)domain, &gerr ) )
583
fprintf( stderr, "%s\n", gerr->message );
584
g_clear_error( &gerr );
590
fprintf( stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING );
594
if( configDir == NULL )
595
configDir = (char*) tr_getDefaultConfigDir( MY_CONFIG_NAME );
598
didinit = cf_init( configDir, NULL ); /* must come before actions_init */
600
setupsighandlers( ); /* set up handlers for fatal signals */
602
didlock = cf_lock( &tr_state, &err );
603
argfiles = checkfilenames( argc - 1, argv + 1 );
605
if( !didlock && argfiles )
607
/* We have torrents to add but there's another copy of Transmsision
608
* running... chances are we've been invoked from a browser, etc.
609
* So send the files over to the "real" copy of Transmission, and
610
* if that goes well, then our work is done. */
612
gboolean delegated = FALSE;
613
const gboolean trash_originals = pref_flag_get( TR_PREFS_KEY_TRASH_ORIGINAL );
615
for( l=argfiles; l!=NULL; l=l->next )
617
const char * filename = l->data;
618
const gboolean added = gtr_dbus_add_torrent( filename );
620
if( added && trash_originals )
621
gtr_file_trash_or_remove( filename );
627
g_slist_foreach( argfiles, (GFunc)g_free, NULL );
628
g_slist_free( argfiles );
637
else if( ( !didlock ) && ( tr_state == GTR_LOCKFILE_ELOCK ) )
639
/* There's already another copy of Transmission running,
640
* so tell it to present its window to the user */
642
if( !gtr_dbus_present_window( ) )
643
err = g_strdup( _( "Transmission is already running, but is not responding. To start a new session, you must first close the existing Transmission process." ) );
646
if( didlock && ( didinit || cf_init( configDir, &err ) ) )
648
/* No other copy of Transmission running...
649
* so we're going to be the primary. */
653
GtkUIManager * myUIManager;
654
tr_session * session;
655
struct cbdata * cbdata = g_new0( struct cbdata, 1 );
657
sighandler_cbdata = cbdata;
659
/* ensure the directories are created */
660
if(( str = pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR )))
661
gtr_mkdir_with_parents( str, 0777 );
662
if(( str = pref_string_get( TR_PREFS_KEY_INCOMPLETE_DIR )))
663
gtr_mkdir_with_parents( str, 0777 );
665
/* initialize the libtransmission session */
666
session = tr_sessionInit( "gtk", configDir, TRUE, pref_get_all( ) );
667
pref_flag_set( TR_PREFS_KEY_ALT_SPEED_ENABLED, tr_sessionUsesAltSpeed( session ) );
668
pref_int_set( TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort( session ) );
669
cbdata->core = tr_core_new( session );
671
/* init the ui manager */
672
myUIManager = gtk_ui_manager_new ( );
673
actions_init ( myUIManager, cbdata );
674
gtk_ui_manager_add_ui_from_string ( myUIManager, fallback_ui_file, -1, NULL );
676
launchpad_integration_add_ui ( myUIManager,
677
"/main-window-menu/help-menu/launchpad-items" );
679
gtk_ui_manager_ensure_update ( myUIManager );
680
gtk_window_set_default_icon_name ( MY_CONFIG_NAME );
682
/* create main window now to be a parent to any error dialogs */
683
win = GTK_WINDOW( tr_window_new( myUIManager, cbdata->core ) );
684
g_signal_connect( win, "size-allocate", G_CALLBACK( onMainWindowSizeAllocated ), cbdata );
686
appsetup( win, argfiles, cbdata, startpaused, startminimized );
687
tr_sessionSetRPCCallback( session, onRPCChanged, cbdata );
689
/* on startup, check & see if it's time to update the blocklist */
690
if( pref_flag_get( TR_PREFS_KEY_BLOCKLIST_ENABLED ) ) {
691
if( pref_flag_get( PREF_KEY_BLOCKLIST_UPDATES_ENABLED ) ) {
692
const int64_t last_time = pref_int_get( "blocklist-date" );
693
const int SECONDS_IN_A_WEEK = 7 * 24 * 60 * 60;
694
const time_t now = time( NULL );
695
if( last_time + SECONDS_IN_A_WEEK < now )
696
tr_core_blocklist_update( cbdata->core );
700
/* if there's no magnet link handler registered, register us */
701
registerMagnetLinkHandler( );
707
const char * primary_text = _( "Transmission cannot be started." );
708
GtkWidget * w = gtk_message_dialog_new( NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, primary_text, NULL );
709
gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), "%s", err );
710
g_signal_connect( w, "response", G_CALLBACK(gtk_main_quit), NULL );
711
gtk_widget_show( w );
720
onCoreBusy( TrCore * core UNUSED, gboolean busy, struct cbdata * c )
722
tr_window_set_busy( c->wind, busy );
726
appsetup( TrWindow * wind,
727
GSList * torrentFiles,
728
struct cbdata * cbdata,
730
gboolean isIconified )
732
const pref_flag_t start =
733
forcepause ? PREF_FLAG_FALSE : PREF_FLAG_DEFAULT;
734
const pref_flag_t prompt = PREF_FLAG_DEFAULT;
736
/* fill out cbdata */
739
cbdata->msgwin = NULL;
740
cbdata->prefs = NULL;
742
cbdata->isClosing = 0;
743
cbdata->errqueue = NULL;
744
cbdata->dupqueue = NULL;
745
cbdata->isIconified = isIconified;
748
pref_flag_set( PREF_KEY_SHOW_TRAY_ICON, TRUE );
750
actions_set_core( cbdata->core );
752
/* set up core handlers */
753
g_signal_connect( cbdata->core, "busy", G_CALLBACK( onCoreBusy ), cbdata );
754
g_signal_connect( cbdata->core, "add-error", G_CALLBACK( coreerr ), cbdata );
755
g_signal_connect( cbdata->core, "add-prompt", G_CALLBACK( onAddTorrent ), cbdata );
756
g_signal_connect( cbdata->core, "prefs-changed", G_CALLBACK( prefschanged ), cbdata );
757
g_signal_connect_swapped( cbdata->core, "quit", G_CALLBACK( wannaquit ), cbdata );
759
/* add torrents from command-line and saved state */
760
tr_core_load( cbdata->core, forcepause );
761
tr_core_add_list( cbdata->core, torrentFiles, start, prompt, TRUE );
763
tr_core_torrents_added( cbdata->core );
765
/* set up main window */
766
winsetup( cbdata, wind );
768
/* set up the icon */
769
prefschanged( cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata );
771
/* start model update timer */
772
cbdata->timer = gtr_timeout_add_seconds( MAIN_WINDOW_REFRESH_INTERVAL_SECONDS, updatemodel, cbdata );
773
updatemodel( cbdata );
775
/* either show the window or iconify it */
777
gtk_widget_show( GTK_WIDGET( wind ) );
780
gtk_window_set_skip_taskbar_hint( cbdata->wind,
781
cbdata->icon != NULL );
782
cbdata->isIconified = FALSE; // ensure that the next toggle iconifies
783
action_toggle( "toggle-main-window", FALSE );
786
if( !pref_flag_get( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT ) )
788
GtkWidget * w = gtk_message_dialog_new( GTK_WINDOW( wind ),
789
GTK_DIALOG_DESTROY_WITH_PARENT,
793
_( "Transmission is a file-sharing program. When you run a torrent, its data will be made available to others by means of upload. You and you alone are fully responsible for exercising proper judgement and abiding by your local laws." ) );
794
gtk_dialog_add_button( GTK_DIALOG( w ), GTK_STOCK_QUIT, GTK_RESPONSE_REJECT );
795
gtk_dialog_add_button( GTK_DIALOG( w ), _( "I _Accept" ), GTK_RESPONSE_ACCEPT );
796
gtk_dialog_set_default_response( GTK_DIALOG( w ), GTK_RESPONSE_ACCEPT );
797
switch( gtk_dialog_run( GTK_DIALOG( w ) ) ) {
798
case GTK_RESPONSE_ACCEPT:
799
/* only show it once */
800
pref_flag_set( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT, TRUE );
801
gtk_widget_destroy( w );
810
tr_window_present( GtkWindow * window )
812
#if GTK_CHECK_VERSION( 2, 8, 0 )
813
gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
815
gtk_window_present( window );
820
toggleMainWindow( struct cbdata * cbdata )
822
GtkWindow * window = GTK_WINDOW( cbdata->wind );
823
const int doShow = cbdata->isIconified;
829
cbdata->isIconified = 0;
830
gtk_window_set_skip_taskbar_hint( window, FALSE );
831
gtk_window_move( window, x, y );
832
gtr_widget_set_visible( GTK_WIDGET( window ), TRUE );
833
tr_window_present( window );
837
gtk_window_get_position( window, &x, &y );
838
gtk_window_set_skip_taskbar_hint( window, TRUE );
839
gtr_widget_set_visible( GTK_WIDGET( window ), FALSE );
840
cbdata->isIconified = 1;
845
shouldConfirmBeforeExiting( struct cbdata * data )
847
if( !pref_flag_get( PREF_KEY_ASKQUIT ) )
850
struct counts_data counts;
851
getTorrentCounts( data, &counts );
852
return counts.activeCount > 0;
857
maybeaskquit( struct cbdata * cbdata )
859
if( !shouldConfirmBeforeExiting( cbdata ) )
862
if( cbdata->quit_dialog == NULL )
863
cbdata->quit_dialog = askquit( cbdata->core, cbdata->wind, wannaquit, cbdata );
864
gtk_window_present( GTK_WINDOW( cbdata->quit_dialog ) );
869
winclose( GtkWidget * w UNUSED,
870
GdkEvent * event UNUSED,
873
struct cbdata * cbdata = gdata;
875
if( cbdata->icon != NULL )
876
action_activate ( "toggle-main-window" );
878
maybeaskquit( cbdata );
880
return TRUE; /* don't propagate event further */
884
rowChangedCB( GtkTreeModel * model UNUSED,
886
GtkTreeIter * iter UNUSED,
889
struct cbdata * data = gdata;
890
if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
891
refreshActions( gdata );
895
on_drag_data_received( GtkWidget * widget UNUSED,
896
GdkDragContext * drag_context,
899
GtkSelectionData * selection_data,
905
gboolean success = FALSE;
906
GSList * filenames = NULL;
907
struct cbdata * data = gdata;
908
char ** uris = gtk_selection_data_get_uris( selection_data );
910
/* try to add the filename URIs... */
911
for( i=0; uris && uris[i]; ++i )
913
const char * uri = uris[i];
914
char * filename = g_filename_from_uri( uri, NULL, NULL );
916
if( filename && g_file_test( filename, G_FILE_TEST_EXISTS ) )
918
filenames = g_slist_append( filenames, g_strdup( filename ) );
921
else if( tr_urlIsValid( uri, -1 ) || gtr_is_magnet_link( uri ) )
923
tr_core_add_from_url( data->core, uri );
929
tr_core_add_list_defaults( data->core, g_slist_reverse( filenames ), TRUE );
931
tr_core_torrents_added( data->core );
932
gtk_drag_finish( drag_context, success, FALSE, time_ );
939
winsetup( struct cbdata * cbdata, TrWindow * wind )
942
GtkTreeModel * model;
943
GtkTreeSelection * sel;
945
g_assert( NULL == cbdata->wind );
946
cbdata->wind = GTK_WINDOW( wind );
947
cbdata->sel = sel = GTK_TREE_SELECTION( tr_window_get_selection( cbdata->wind ) );
949
g_signal_connect( sel, "changed", G_CALLBACK( selectionChangedCB ), cbdata );
950
selectionChangedCB( sel, cbdata );
951
model = tr_core_model( cbdata->core );
952
g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
953
g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
954
refreshActions( cbdata );
956
/* register to handle URIs that get dragged onto our main window */
957
w = GTK_WIDGET( wind );
958
gtk_drag_dest_set( w, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY );
959
gtk_drag_dest_add_uri_targets( w );
960
g_signal_connect( w, "drag-data-received", G_CALLBACK(on_drag_data_received), cbdata );
964
onSessionClosed( gpointer gdata )
966
struct cbdata * cbdata = gdata;
968
/* shutdown the gui */
969
while( cbdata->details != NULL ) {
970
struct DetailsDialogHandle * h = cbdata->details->data;
971
gtk_widget_destroy( h->dialog );
975
gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
977
gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
978
g_object_unref( cbdata->core );
980
g_object_unref( cbdata->icon );
981
if( cbdata->errqueue ) {
982
g_slist_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
983
g_slist_free( cbdata->errqueue );
985
if( cbdata->dupqueue ) {
986
g_slist_foreach( cbdata->dupqueue, (GFunc)g_free, NULL );
987
g_slist_free( cbdata->dupqueue );
997
sessionCloseThreadFunc( gpointer gdata )
999
/* since tr_sessionClose() is a blocking function,
1000
* call it from another thread... when it's done,
1001
* punt the GUI teardown back to the GTK+ thread */
1002
struct cbdata * cbdata = gdata;
1003
gdk_threads_enter( );
1004
tr_core_close( cbdata->core );
1005
gtr_idle_add( onSessionClosed, gdata );
1006
gdk_threads_leave( );
1011
do_exit_cb( GtkWidget *w UNUSED,
1012
gpointer data UNUSED )
1018
wannaquit( gpointer vdata )
1020
GtkWidget *r, *p, *b, *w, *c;
1021
struct cbdata *cbdata = vdata;
1023
/* stop the update timer */
1026
g_source_remove( cbdata->timer );
1030
c = GTK_WIDGET( cbdata->wind );
1031
gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
1033
r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
1034
gtk_container_add( GTK_CONTAINER( c ), r );
1036
p = gtk_table_new( 3, 2, FALSE );
1037
gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
1038
gtk_container_add( GTK_CONTAINER( r ), p );
1040
w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
1041
gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
1043
w = gtk_label_new( NULL );
1044
gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
1045
gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1046
gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
1048
w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
1049
gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
1050
gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
1052
b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
1053
w = gtk_button_new_with_mnemonic( _( "_Quit Now" ) );
1054
g_signal_connect( w, "clicked", G_CALLBACK( do_exit_cb ), NULL );
1055
gtk_container_add( GTK_CONTAINER( b ), w );
1056
gtk_table_attach( GTK_TABLE( p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
1058
gtk_widget_show_all( r );
1059
gtk_widget_grab_focus( w );
1062
gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
1064
/* ensure the window is in its previous position & size.
1065
* this seems to be necessary because changing the main window's
1066
* child seems to unset the size */
1067
gtk_window_resize( cbdata->wind, pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
1068
pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
1069
gtk_window_move( cbdata->wind, pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
1070
pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
1072
/* shut down libT */
1073
g_thread_create( sessionCloseThreadFunc, vdata, TRUE, NULL );
1077
flushAddTorrentErrors( GtkWindow * window,
1078
const char * primary,
1081
GString * s = g_string_new( NULL );
1085
if( g_slist_length( *files ) > 1 ) {
1086
for( l=*files; l!=NULL; l=l->next )
1087
g_string_append_printf( s, "\xE2\x88\x99 %s\n", (const char*)l->data );
1089
for( l=*files; l!=NULL; l=l->next )
1090
g_string_append_printf( s, "%s\n", (const char*)l->data );
1092
w = gtk_message_dialog_new( window,
1093
GTK_DIALOG_DESTROY_WITH_PARENT,
1097
gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
1099
g_signal_connect_swapped( w, "response",
1100
G_CALLBACK( gtk_widget_destroy ), w );
1101
gtk_widget_show_all( w );
1102
g_string_free( s, TRUE );
1104
g_slist_foreach( *files, (GFunc)g_free, NULL );
1105
g_slist_free( *files );
1110
showTorrentErrors( struct cbdata * cbdata )
1112
if( cbdata->errqueue )
1113
flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
1114
gtr_ngettext( "Couldn't add corrupt torrent",
1115
"Couldn't add corrupt torrents",
1116
g_slist_length( cbdata->errqueue ) ),
1117
&cbdata->errqueue );
1119
if( cbdata->dupqueue )
1120
flushAddTorrentErrors( GTK_WINDOW( cbdata->wind ),
1121
gtr_ngettext( "Couldn't add duplicate torrent",
1122
"Couldn't add duplicate torrents",
1123
g_slist_length( cbdata->dupqueue ) ),
1124
&cbdata->dupqueue );
1128
coreerr( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
1134
g_slist_append( c->errqueue, g_path_get_basename( msg ) );
1137
case TR_PARSE_DUPLICATE:
1138
c->dupqueue = g_slist_append( c->dupqueue, g_strdup( msg ) );
1141
case TR_CORE_ERR_NO_MORE_TORRENTS:
1142
showTorrentErrors( c );
1146
g_assert_not_reached( );
1151
#if GTK_CHECK_VERSION( 2, 8, 0 )
1153
on_main_window_focus_in( GtkWidget * widget UNUSED,
1154
GdkEventFocus * event UNUSED,
1157
struct cbdata * cbdata = gdata;
1160
gtk_window_set_urgency_hint( GTK_WINDOW( cbdata->wind ), FALSE );
1166
onAddTorrent( TrCore * core,
1170
struct cbdata * cbdata = gdata;
1171
GtkWidget * w = addSingleTorrentDialog( cbdata->wind, core, ctor );
1173
#if GTK_CHECK_VERSION( 2, 8, 0 )
1174
g_signal_connect( w, "focus-in-event",
1175
G_CALLBACK( on_main_window_focus_in ), cbdata );
1177
gtk_window_set_urgency_hint( cbdata->wind, TRUE );
1182
prefschanged( TrCore * core UNUSED, const char * key, gpointer data )
1184
struct cbdata * cbdata = data;
1185
tr_session * tr = tr_core_session( cbdata->core );
1187
if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
1189
tr_sessionSetEncryption( tr, pref_int_get( key ) );
1191
else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
1193
tr_sessionSetDownloadDir( tr, pref_string_get( key ) );
1195
else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
1197
tr_setMessageLevel( pref_int_get( key ) );
1199
else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1201
tr_sessionSetPeerPort( tr, pref_int_get( key ) );
1203
else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
1205
tr_blocklistSetEnabled( tr, pref_flag_get( key ) );
1207
else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_URL ) )
1209
tr_blocklistSetURL( tr, pref_string_get( key ) );
1211
else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
1213
const int show = pref_flag_get( key );
1214
if( show && !cbdata->icon )
1215
cbdata->icon = tr_icon_new( cbdata->core );
1216
else if( !show && cbdata->icon ) {
1217
g_object_unref( cbdata->icon );
1218
cbdata->icon = NULL;
1221
else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
1223
tr_sessionLimitSpeed( tr, TR_DOWN, pref_flag_get( key ) );
1225
else if( !strcmp( key, TR_PREFS_KEY_DSPEED_KBps ) )
1227
tr_sessionSetSpeedLimit_KBps( tr, TR_DOWN, pref_int_get( key ) );
1229
else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
1231
tr_sessionLimitSpeed( tr, TR_UP, pref_flag_get( key ) );
1233
else if( !strcmp( key, TR_PREFS_KEY_USPEED_KBps ) )
1235
tr_sessionSetSpeedLimit_KBps( tr, TR_UP, pref_int_get( key ) );
1237
else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
1239
tr_sessionSetRatioLimited( tr, pref_flag_get( key ) );
1241
else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
1243
tr_sessionSetRatioLimit( tr, pref_double_get( key ) );
1245
else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT ) )
1247
tr_sessionSetIdleLimit( tr, pref_int_get( key ) );
1249
else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT_ENABLED ) )
1251
tr_sessionSetIdleLimited( tr, pref_flag_get( key ) );
1253
else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
1255
tr_sessionSetPortForwardingEnabled( tr, pref_flag_get( key ) );
1257
else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
1259
tr_sessionSetPexEnabled( tr, pref_flag_get( key ) );
1261
else if( !strcmp( key, TR_PREFS_KEY_RENAME_PARTIAL_FILES ) )
1263
tr_sessionSetIncompleteFileNamingEnabled( tr, pref_flag_get( key ) );
1265
else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
1267
tr_sessionSetDHTEnabled( tr, pref_flag_get( key ) );
1269
else if( !strcmp( key, TR_PREFS_KEY_LPD_ENABLED ) )
1271
tr_sessionSetLPDEnabled( tr, pref_flag_get( key ) );
1273
else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
1275
tr_sessionSetRPCPort( tr, pref_int_get( key ) );
1277
else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
1279
tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
1281
else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
1283
tr_sessionSetRPCWhitelist( tr, pref_string_get( key ) );
1285
else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
1287
tr_sessionSetRPCWhitelistEnabled( tr, pref_flag_get( key ) );
1289
else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
1291
tr_sessionSetRPCUsername( tr, pref_string_get( key ) );
1293
else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
1295
tr_sessionSetRPCPassword( tr, pref_string_get( key ) );
1297
else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
1299
tr_sessionSetRPCPasswordEnabled( tr, pref_flag_get( key ) );
1301
else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP_KBps ) )
1303
tr_sessionSetAltSpeed_KBps( tr, TR_UP, pref_int_get( key ) );
1305
else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps ) )
1307
tr_sessionSetAltSpeed_KBps( tr, TR_DOWN, pref_int_get( key ) );
1309
else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
1311
const gboolean b = pref_flag_get( key );
1312
tr_sessionUseAltSpeed( tr, b );
1313
action_toggle( key, b );
1315
else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
1317
tr_sessionSetAltSpeedBegin( tr, pref_int_get( key ) );
1319
else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
1321
tr_sessionSetAltSpeedEnd( tr, pref_int_get( key ) );
1323
else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
1325
tr_sessionUseAltSpeedTime( tr, pref_flag_get( key ) );
1327
else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
1329
tr_sessionSetAltSpeedDay( tr, pref_int_get( key ) );
1331
else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
1333
tr_sessionSetPeerPortRandomOnStart( tr, pref_flag_get( key ) );
1335
else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR ) )
1337
tr_sessionSetIncompleteDir( tr, pref_string_get( key ) );
1339
else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) )
1341
tr_sessionSetIncompleteDirEnabled( tr, pref_flag_get( key ) );
1343
else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) )
1345
tr_sessionSetTorrentDoneScriptEnabled( tr, pref_flag_get( key ) );
1347
else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME ) )
1349
tr_sessionSetTorrentDoneScript( tr, pref_string_get( key ) );
1351
else if( !strcmp( key, TR_PREFS_KEY_START) )
1353
tr_sessionSetPaused( tr, !pref_flag_get( key ) );
1355
else if( !strcmp( key, TR_PREFS_KEY_TRASH_ORIGINAL ) )
1357
tr_sessionSetDeleteSource( tr, pref_flag_get( key ) );
1362
updatemodel( gpointer gdata )
1364
struct cbdata *data = gdata;
1365
const gboolean done = data->isClosing || global_sigcount;
1369
/* update the torrent data in the model */
1370
tr_core_update( data->core );
1372
/* update the main window's statusbar and toolbar buttons */
1373
if( data->wind != NULL )
1374
tr_window_update( data->wind );
1376
/* update the actions */
1377
refreshActions( data );
1379
/* update the status tray icon */
1380
if( data->icon != NULL )
1381
tr_icon_refresh( data->icon );
1388
onUriClicked( GtkAboutDialog * u UNUSED, const gchar * uri, gpointer u2 UNUSED )
1390
gtr_open_uri( uri );
1394
about( GtkWindow * parent )
1397
const char * website_uri = "http://www.transmissionbt.com/";
1398
const char * authors[] = {
1399
"Charles Kerr (Backend; GTK+)",
1400
"Mitchell Livingston (Backend; OS X)",
1404
gtk_about_dialog_set_url_hook( onUriClicked, NULL, NULL );
1406
d = g_object_new( GTK_TYPE_ABOUT_DIALOG,
1408
"comments", _( "A fast and easy BitTorrent client" ),
1409
"copyright", _( "Copyright (c) The Transmission Project" ),
1410
"logo-icon-name", MY_CONFIG_NAME,
1411
"name", g_get_application_name( ),
1412
/* Translators: translate "translator-credits" as your name
1413
to have it appear in the credits in the "About"
1415
"translator-credits", _( "translator-credits" ),
1416
"version", LONG_VERSION_STRING,
1417
"website", website_uri,
1418
"website-label", website_uri,
1421
"wrap-license", TRUE,
1424
gtk_window_set_transient_for( GTK_WINDOW( d ), parent );
1425
g_signal_connect_swapped( d, "response", G_CALLBACK (gtk_widget_destroy), d );
1426
gtk_widget_show_all( d );
1430
appendIdToBencList( GtkTreeModel * m, GtkTreePath * path UNUSED,
1431
GtkTreeIter * iter, gpointer list )
1433
tr_torrent * tor = NULL;
1434
gtk_tree_model_get( m, iter, MC_TORRENT_RAW, &tor, -1 );
1435
tr_bencListAddInt( list, tr_torrentId( tor ) );
1439
rpcOnSelectedTorrents( struct cbdata * data, const char * method )
1441
tr_benc top, *args, *ids;
1442
gboolean invoked = FALSE;
1443
tr_session * session = tr_core_session( data->core );
1444
GtkTreeSelection * s = tr_window_get_selection( data->wind );
1446
tr_bencInitDict( &top, 2 );
1447
tr_bencDictAddStr( &top, "method", method );
1448
args = tr_bencDictAddDict( &top, "arguments", 1 );
1449
ids = tr_bencDictAddList( args, "ids", 0 );
1450
gtk_tree_selection_selected_foreach( s, appendIdToBencList, ids );
1452
if( tr_bencListSize( ids ) != 0 )
1455
char * json = tr_bencToStr( &top, TR_FMT_JSON_LEAN, &json_len );
1456
tr_rpc_request_exec_json( session, json, json_len, NULL, NULL );
1461
tr_bencFree( &top );
1466
openFolderForeach( GtkTreeModel * model,
1467
GtkTreePath * path UNUSED,
1469
gpointer user_data UNUSED )
1471
TrTorrent * gtor = NULL;
1473
gtk_tree_model_get( model, iter, MC_TORRENT, >or, -1 );
1474
tr_torrent_open_folder( gtor );
1475
g_object_unref( G_OBJECT( gtor ) );
1479
msgwinclosed( void )
1481
action_toggle( "toggle-message-log", FALSE );
1486
accumulateSelectedTorrents( GtkTreeModel * model,
1487
GtkTreePath * path UNUSED,
1491
GSList ** data = ( GSList** ) gdata;
1492
TrTorrent * gtor = NULL;
1494
gtk_tree_model_get( model, iter, MC_TORRENT, >or, -1 );
1495
*data = g_slist_prepend( *data, gtor );
1496
g_object_unref( G_OBJECT( gtor ) );
1500
removeSelected( struct cbdata * data, gboolean delete_files )
1503
GtkTreeSelection * s = tr_window_get_selection( data->wind );
1505
gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
1508
l = g_slist_reverse( l );
1509
confirmRemove( data->wind, data->core, l, delete_files );
1514
startAllTorrents( struct cbdata * data )
1516
tr_session * session = tr_core_session( data->core );
1517
const char * cmd = "{ \"method\": \"torrent-start\" }";
1518
tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1522
pauseAllTorrents( struct cbdata * data )
1524
tr_session * session = tr_core_session( data->core );
1525
const char * cmd = "{ \"method\": \"torrent-stop\" }";
1526
tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
1530
getFirstSelectedTorrent( struct cbdata * data )
1532
tr_torrent * tor = NULL;
1533
GtkTreeSelection * s = tr_window_get_selection( data->wind );
1535
GList * l = gtk_tree_selection_get_selected_rows( s, &m );
1537
GtkTreePath * p = l->data;
1539
if( gtk_tree_model_get_iter( m, &i, p ) )
1540
gtk_tree_model_get( m, &i, MC_TORRENT_RAW, &tor, -1 );
1542
g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
1548
detailsClosed( gpointer gdata, GObject * dead )
1550
struct cbdata * data = gdata;
1551
struct DetailsDialogHandle * h = findDetailsDialogFromWidget( data, dead );
1555
data->details = g_slist_remove( data->details, h );
1562
copyMagnetLinkToClipboard( GtkWidget * w, tr_torrent * tor )
1564
char * magnet = tr_torrentGetMagnetLink( tor );
1565
GdkDisplay * display = gtk_widget_get_display( w );
1567
GtkClipboard * clipboard;
1569
/* this is The Right Thing for copy/paste... */
1570
selection = GDK_SELECTION_CLIPBOARD;
1571
clipboard = gtk_clipboard_get_for_display( display, selection );
1572
gtk_clipboard_set_text( clipboard, magnet, -1 );
1574
/* ...but people using plain ol' X need this instead */
1575
selection = GDK_SELECTION_PRIMARY;
1576
clipboard = gtk_clipboard_get_for_display( display, selection );
1577
gtk_clipboard_set_text( clipboard, magnet, -1 );
1584
doAction( const char * action_name, gpointer user_data )
1586
struct cbdata * data = user_data;
1587
gboolean changed = FALSE;
1589
if( !strcmp( action_name, "add-torrent-from-url" ) )
1591
addURLDialog( data->wind, data->core );
1593
else if( !strcmp( action_name, "add-torrent-menu" )
1594
|| !strcmp( action_name, "add-torrent-toolbar" ) )
1596
addDialog( data->wind, data->core );
1598
else if( !strcmp( action_name, "show-stats" ) )
1600
GtkWidget * dialog = stats_dialog_create( data->wind, data->core );
1601
gtk_widget_show( dialog );
1603
else if( !strcmp( action_name, "donate" ) )
1605
gtr_open_uri( "http://www.transmissionbt.com/donate.php" );
1607
else if( !strcmp( action_name, "pause-all-torrents" ) )
1609
pauseAllTorrents( data );
1611
else if( !strcmp( action_name, "start-all-torrents" ) )
1613
startAllTorrents( data );
1615
else if( !strcmp( action_name, "copy-magnet-link-to-clipboard" ) )
1617
tr_torrent * tor = getFirstSelectedTorrent( data );
1620
copyMagnetLinkToClipboard( GTK_WIDGET( data->wind ), tor );
1623
else if( !strcmp( action_name, "relocate-torrent" ) )
1625
GSList * ids = getSelectedTorrentIds( data );
1628
GtkWindow * parent = GTK_WINDOW( data->wind );
1629
GtkWidget * w = gtr_relocate_dialog_new( parent, data->core, ids );
1630
gtk_widget_show( w );
1633
else if( !strcmp( action_name, "start-torrent" ) )
1635
changed |= rpcOnSelectedTorrents( data, "torrent-start" );
1637
else if( !strcmp( action_name, "pause-torrent" ) )
1639
changed |= rpcOnSelectedTorrents( data, "torrent-stop" );
1641
else if( !strcmp( action_name, "verify-torrent" ) )
1643
changed |= rpcOnSelectedTorrents( data, "torrent-verify" );
1645
else if( !strcmp( action_name, "update-tracker" ) )
1647
changed |= rpcOnSelectedTorrents( data, "torrent-reannounce" );
1649
else if( !strcmp( action_name, "open-torrent-folder" ) )
1651
GtkTreeSelection * s = tr_window_get_selection( data->wind );
1652
gtk_tree_selection_selected_foreach( s, openFolderForeach, data );
1654
else if( !strcmp( action_name, "show-torrent-properties" ) )
1657
GSList * ids = getSelectedTorrentIds( data );
1658
struct DetailsDialogHandle * h = findDetailsDialogFromIds( data, ids );
1662
h = g_new( struct DetailsDialogHandle, 1 );
1663
h->key = getDetailsDialogKey( ids );
1664
h->dialog = w = torrent_inspector_new( GTK_WINDOW( data->wind ), data->core );
1665
torrent_inspector_set_torrents( w, ids );
1666
data->details = g_slist_append( data->details, h );
1667
g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
1669
gtk_window_present( GTK_WINDOW( w ) );
1670
g_slist_free( ids );
1672
else if( !strcmp( action_name, "new-torrent" ) )
1674
GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ), data->core );
1675
gtk_widget_show_all( w );
1677
else if( !strcmp( action_name, "remove-torrent" ) )
1679
removeSelected( data, FALSE );
1681
else if( !strcmp( action_name, "delete-torrent" ) )
1683
removeSelected( data, TRUE );
1685
else if( !strcmp( action_name, "quit" ) )
1687
maybeaskquit( data );
1689
else if( !strcmp( action_name, "select-all" ) )
1691
GtkTreeSelection * s = tr_window_get_selection( data->wind );
1692
gtk_tree_selection_select_all( s );
1694
else if( !strcmp( action_name, "deselect-all" ) )
1696
GtkTreeSelection * s = tr_window_get_selection( data->wind );
1697
gtk_tree_selection_unselect_all( s );
1699
else if( !strcmp( action_name, "edit-preferences" ) )
1701
if( NULL == data->prefs )
1703
data->prefs = tr_prefs_dialog_new( G_OBJECT( data->core ),
1705
g_signal_connect( data->prefs, "destroy",
1706
G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1707
gtk_widget_show( GTK_WIDGET( data->prefs ) );
1710
else if( !strcmp( action_name, "toggle-message-log" ) )
1714
GtkWidget * win = msgwin_new( data->core, data->wind );
1715
g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ), NULL );
1720
action_toggle( "toggle-message-log", FALSE );
1721
gtk_widget_destroy( data->msgwin );
1722
data->msgwin = NULL;
1725
else if( !strcmp( action_name, "show-about-dialog" ) )
1727
about( data->wind );
1729
else if( !strcmp ( action_name, "help" ) )
1731
gtr_open_uri( gtr_get_help_uri( ) );
1733
else if( !strcmp( action_name, "toggle-main-window" ) )
1735
toggleMainWindow( data );
1737
else g_error ( "Unhandled action: %s", action_name );
1740
updatemodel( data );