~ubuntu-branches/ubuntu/raring/transmission/raring

« back to all changes in this revision

Viewing changes to .pc/0108-sync_web_interface_turtle_mode_with_gui.patch/gtk/main.c

  • Committer: Bazaar Package Importer
  • Author(s): Krzysztof Klimonda
  • Date: 2011-03-22 18:59:19 UTC
  • Revision ID: james.westby@ubuntu.com-20110322185919-ceizoba779xccx1r
Tags: 2.13-0ubuntu7
* debian/patches/0108-sync_web_interface_turtle_mode_with_gui.patch
  - Keep GUI and web interface in sync when toggling speed limit mode.
    (LP: #727629)
* debian/patches/0109-fix_crash_on_missing_script.patch (LP: #710003)
  - Fix crash when the configured script is not found.
* debian/patches/0110-dont_truncate_string.patch:
  - Don't truncace string when transmission-edit -r option is used. 
* debian/patches/0111-call_guessPacketOverhead_for_reads.patch
  - Fix peer-io code so packet overhead is calculated for socket reads.
* 0112-fix_check_for_result_of_tr_torrentGetMetadataPercent.patch:
  - tr_torrentGetMetadataPercent() may return NULL; fix the check in
    torrent-magnet.c to handle it.
* debian/patches/0113-fix_support_for_ipv6_trackers.patch:
  - Fix support for IPv6-only trackers.
* debian/patches/0114-dont_move_file_if_src_and_dest_are_the_same.patch:
  - Don't move files and directories if the destination path is the same
    as origin.
* debian/patches/0115-fix_tall_argument.patch:
  - Fix the "-tall" argument in transmission-remote.
* debian/patches/0116-fix_segfault_in_gtk_client.patch:
  - Fix crash in the Gtk+ client when adding many torrents remotely.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/******************************************************************************
 
2
 * $Id: main.c 11368 2010-10-31 17:16:12Z charles $
 
3
 *
 
4
 * Copyright (c) 2005-2008 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 <sys/param.h>
 
27
#include <signal.h>
 
28
#include <string.h>
 
29
#include <stdio.h>
 
30
#include <stdlib.h>
 
31
#include <time.h>
 
32
#include <unistd.h>
 
33
 
 
34
#include <gtk/gtk.h>
 
35
#include <glib/gi18n.h>
 
36
#include <glib/gstdio.h>
 
37
 
 
38
#ifdef HAVE_LPI
 
39
#include <launchpad-integration.h>
 
40
#endif
 
41
 
 
42
#include <libtransmission/transmission.h>
 
43
#include <libtransmission/rpcimpl.h>
 
44
#include <libtransmission/utils.h>
 
45
#include <libtransmission/version.h>
 
46
 
 
47
#include "actions.h"
 
48
#include "add-dialog.h"
 
49
#include "conf.h"
 
50
#include "details.h"
 
51
#include "dialogs.h"
 
52
#include "hig.h"
 
53
#include "makemeta-ui.h"
 
54
#include "msgwin.h"
 
55
#include "notify.h"
 
56
#include "relocate.h"
 
57
#include "stats.h"
 
58
#include "tr-core.h"
 
59
#include "tr-icon.h"
 
60
#include "tr-prefs.h"
 
61
#include "tr-torrent.h"
 
62
#include "tr-window.h"
 
63
#include "util.h"
 
64
#include "ui.h"
 
65
 
 
66
#define MY_CONFIG_NAME "transmission"
 
67
#define MY_READABLE_NAME "transmission-gtk"
 
68
 
 
69
#if GTK_CHECK_VERSION( 2, 8, 0 )
 
70
 #define SHOW_LICENSE
 
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.";
 
76
#endif
 
77
 
 
78
struct cbdata
 
79
{
 
80
    gboolean            isIconified;
 
81
    gboolean            isClosing;
 
82
    guint               timer;
 
83
    gpointer            icon;
 
84
    GtkWindow         * wind;
 
85
    TrCore            * core;
 
86
    GtkWidget         * msgwin;
 
87
    GtkWidget         * prefs;
 
88
    GSList            * errqueue;
 
89
    GSList            * dupqueue;
 
90
    GSList            * details;
 
91
    GtkTreeSelection  * sel;
 
92
    GtkWidget         * quit_dialog;
 
93
};
 
94
 
 
95
/**
 
96
***
 
97
**/
 
98
 
 
99
static int
 
100
compareInts( const void * a, const void * b )
 
101
{
 
102
    return *(int*)a - *(int*)b;
 
103
}
 
104
 
 
105
static char*
 
106
getDetailsDialogKey( GSList * id_list )
 
107
{
 
108
    int i;
 
109
    int n;
 
110
    int * ids;
 
111
    GSList * l;
 
112
    GString * gstr = g_string_new( NULL );
 
113
 
 
114
    n = g_slist_length( id_list );
 
115
    ids = g_new( int, n );
 
116
    i = 0;
 
117
    for( l=id_list; l!=NULL; l=l->next )
 
118
        ids[i++] = GPOINTER_TO_INT( l->data );
 
119
    g_assert( i == n );
 
120
    qsort( ids, n, sizeof(int), compareInts );
 
121
 
 
122
    for( i=0; i<n; ++i )
 
123
        g_string_append_printf( gstr, "%d ", ids[i] );
 
124
 
 
125
    g_free( ids );
 
126
    return g_string_free( gstr, FALSE );
 
127
}
 
128
 
 
129
struct DetailsDialogHandle
 
130
{
 
131
    char * key;
 
132
    GtkWidget * dialog;
 
133
};
 
134
 
 
135
static GSList*
 
136
getSelectedTorrentIds( struct cbdata * data )
 
137
{
 
138
    GtkTreeSelection * s;
 
139
    GtkTreeModel * model;
 
140
    GSList * ids = NULL;
 
141
    GList * paths = NULL;
 
142
    GList * l;
 
143
 
 
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 ) {
 
147
        GtkTreeIter iter;
 
148
        if( gtk_tree_model_get_iter( model, &iter, l->data ) ) {
 
149
            tr_torrent * tor;
 
150
            gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
 
151
            ids = g_slist_append( ids, GINT_TO_POINTER( tr_torrentId( tor ) ) );
 
152
        }
 
153
    }
 
154
 
 
155
    /* cleanup */
 
156
    g_list_foreach( paths, (GFunc)gtk_tree_path_free, NULL );
 
157
    g_list_free( paths );
 
158
    return ids;
 
159
}
 
160
 
 
161
static struct DetailsDialogHandle*
 
162
findDetailsDialogFromIds( struct cbdata * cbdata, GSList * ids )
 
163
{
 
164
    GSList * l;
 
165
    struct DetailsDialogHandle * ret = NULL;
 
166
    char * key = getDetailsDialogKey( ids );
 
167
 
 
168
    for( l=cbdata->details; l!=NULL && ret==NULL; l=l->next ) {
 
169
        struct DetailsDialogHandle * h = l->data;
 
170
        if( !strcmp( h->key, key ) )
 
171
            ret = h;
 
172
    }
 
173
 
 
174
    g_free( key );
 
175
    return ret;
 
176
}
 
177
 
 
178
static struct DetailsDialogHandle*
 
179
findDetailsDialogFromWidget( struct cbdata * cbdata, gpointer w )
 
180
{
 
181
    GSList * l;
 
182
    struct DetailsDialogHandle * ret = NULL;
 
183
 
 
184
    for( l=cbdata->details; l!=NULL && ret==NULL; l=l->next ) {
 
185
        struct DetailsDialogHandle * h = l->data;
 
186
        if( h->dialog == w )
 
187
            ret = h;
 
188
    }
 
189
 
 
190
    return ret;
 
191
}
 
192
 
 
193
/***
 
194
****
 
195
***/
 
196
 
 
197
static void           appsetup( TrWindow * wind,
 
198
                                GSList *   args,
 
199
                                struct     cbdata *,
 
200
                                gboolean   paused,
 
201
                                gboolean   minimized );
 
202
 
 
203
static void           winsetup( struct cbdata * cbdata,
 
204
                                TrWindow *      wind );
 
205
 
 
206
static void           wannaquit( gpointer vdata );
 
207
 
 
208
static void coreerr( TrCore *, guint, const char *, struct cbdata * );
 
209
 
 
210
static void           onAddTorrent( TrCore *,
 
211
                                    tr_ctor *,
 
212
                                    gpointer );
 
213
 
 
214
static void           prefschanged( TrCore *     core,
 
215
                                    const char * key,
 
216
                                    gpointer     data );
 
217
 
 
218
static gboolean       updatemodel( gpointer gdata );
 
219
 
 
220
/***
 
221
****
 
222
***/
 
223
 
 
224
#ifdef HAVE_GCONF2
 
225
 #include <gconf/gconf.h>
 
226
 #include <gconf/gconf-client.h>
 
227
#endif
 
228
 
 
229
static void
 
230
registerMagnetLinkHandler( void )
 
231
{
 
232
#ifdef HAVE_GCONF2
 
233
    GError * err;
 
234
    GConfValue * value;
 
235
    GConfClient * client = gconf_client_get_default( );
 
236
    const char * key = "/desktop/gnome/url-handlers/magnet/command";
 
237
 
 
238
    /* if there's already a manget handler registered, don't do anything */
 
239
    value = gconf_client_get( client, key, NULL );
 
240
    if( value != NULL )
 
241
    {
 
242
        gconf_value_free( value );
 
243
        return;
 
244
    }
 
245
 
 
246
    err = NULL;
 
247
    if( !gconf_client_set_string( client, key, "transmission '%s'", &err ) )
 
248
    {
 
249
        tr_inf( "Unable to register Transmission as default magnet link handler: \"%s\"", err->message );
 
250
        g_clear_error( &err );
 
251
    }
 
252
    else
 
253
    {
 
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" );
 
257
    }
 
258
#endif
 
259
}
 
260
 
 
261
/***
 
262
****
 
263
***/
 
264
 
 
265
struct counts_data
 
266
{
 
267
    int    totalCount;
 
268
    int    activeCount;
 
269
    int    inactiveCount;
 
270
};
 
271
 
 
272
static void
 
273
accumulateStatusForeach( GtkTreeModel *      model,
 
274
                         GtkTreePath  * path UNUSED,
 
275
                         GtkTreeIter *       iter,
 
276
                         gpointer            user_data )
 
277
{
 
278
    int activity = 0;
 
279
    struct counts_data * counts = user_data;
 
280
 
 
281
    ++counts->totalCount;
 
282
 
 
283
    gtk_tree_model_get( model, iter, MC_ACTIVITY, &activity, -1 );
 
284
 
 
285
    if( activity == TR_STATUS_STOPPED )
 
286
        ++counts->inactiveCount;
 
287
    else
 
288
        ++counts->activeCount;
 
289
}
 
290
 
 
291
static void
 
292
getTorrentCounts( struct cbdata * data, struct counts_data * counts )
 
293
{
 
294
    counts->activeCount = 0;
 
295
    counts->inactiveCount = 0;
 
296
    counts->totalCount = 0;
 
297
 
 
298
    gtk_tree_selection_selected_foreach( data->sel, accumulateStatusForeach, counts );
 
299
}
 
300
 
 
301
static void
 
302
accumulateCanUpdateForeach( GtkTreeModel *      model,
 
303
                            GtkTreePath  * path UNUSED,
 
304
                            GtkTreeIter *       iter,
 
305
                            gpointer            accumulated_status )
 
306
{
 
307
    tr_torrent * tor;
 
308
    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
 
309
    *(int*)accumulated_status |= tr_torrentCanManualUpdate( tor );
 
310
}
 
311
 
 
312
static gboolean
 
313
refreshActions( gpointer gdata )
 
314
{
 
315
    int canUpdate;
 
316
    struct counts_data counts;
 
317
    struct cbdata * data = gdata;
 
318
 
 
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 );
 
329
 
 
330
    canUpdate = 0;
 
331
    gtk_tree_selection_selected_foreach( data->sel, accumulateCanUpdateForeach, &canUpdate );
 
332
    action_sensitize( "update-tracker", canUpdate != 0 );
 
333
 
 
334
    {
 
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 );
 
340
    }
 
341
 
 
342
    {
 
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 );
 
347
    }
 
348
 
 
349
    return FALSE;
 
350
}
 
351
 
 
352
static void
 
353
selectionChangedCB( GtkTreeSelection * s UNUSED, gpointer data )
 
354
{
 
355
    gtr_idle_add( refreshActions, data );
 
356
}
 
357
 
 
358
static void
 
359
onMainWindowSizeAllocated( GtkWidget *            window,
 
360
                           GtkAllocation  * alloc UNUSED,
 
361
                           gpointer         gdata UNUSED )
 
362
{
 
363
    const gboolean isMaximized = window->window
 
364
                            && ( gdk_window_get_state( window->window )
 
365
                                 & GDK_WINDOW_STATE_MAXIMIZED );
 
366
 
 
367
    pref_int_set( PREF_KEY_MAIN_WINDOW_IS_MAXIMIZED, isMaximized );
 
368
 
 
369
    if( !isMaximized )
 
370
    {
 
371
        int x, y, w, h;
 
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 );
 
378
    }
 
379
}
 
380
 
 
381
static sig_atomic_t global_sigcount = 0;
 
382
static struct cbdata * sighandler_cbdata = NULL;
 
383
 
 
384
static void
 
385
signal_handler( int sig )
 
386
{
 
387
    if( ++global_sigcount > 1 )
 
388
    {
 
389
        signal( sig, SIG_DFL );
 
390
        raise( sig );
 
391
    }
 
392
    else switch( sig )
 
393
    {
 
394
        case SIGINT:
 
395
        case SIGTERM:
 
396
            g_message( _( "Got signal %d; trying to shut down cleanly.  Do it again if it gets stuck." ), sig );
 
397
            doAction( "quit", sighandler_cbdata );
 
398
            break;
 
399
 
 
400
        default:
 
401
            g_message( "unhandled signal" );
 
402
            break;
 
403
    }
 
404
}
 
405
 
 
406
struct remove_torrent_idle_data
 
407
{
 
408
    TrCore * core;
 
409
    int id;
 
410
};
 
411
 
 
412
static gboolean
 
413
remove_torrent_idle( gpointer gdata )
 
414
{
 
415
    struct remove_torrent_idle_data * data = gdata;
 
416
    tr_core_remove_torrent_from_id( data->core, data->id, FALSE );
 
417
    g_free( data );
 
418
    return FALSE; /* tell g_idle not to call this func twice */
 
419
}
 
420
 
 
421
static void
 
422
setupsighandlers( void )
 
423
{
 
424
    signal( SIGINT, signal_handler );
 
425
    signal( SIGKILL, signal_handler );
 
426
}
 
427
 
 
428
static tr_rpc_callback_status
 
429
onRPCChanged( tr_session            * session,
 
430
              tr_rpc_callback_type    type,
 
431
              struct tr_torrent     * tor,
 
432
              void                  * gdata )
 
433
{
 
434
    tr_rpc_callback_status status = TR_RPC_OK;
 
435
    struct cbdata * cbdata = gdata;
 
436
    gdk_threads_enter( );
 
437
 
 
438
    switch( type )
 
439
    {
 
440
        case TR_RPC_TORRENT_ADDED:
 
441
            tr_core_add_torrent( cbdata->core, tr_torrent_new_preexisting( tor ), TRUE );
 
442
            break;
 
443
 
 
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;
 
450
            break;
 
451
        }
 
452
 
 
453
        case TR_RPC_SESSION_CHANGED: {
 
454
            int i;
 
455
            tr_benc tmp;
 
456
            tr_benc * newval;
 
457
            tr_benc * oldvals = pref_get_all( );
 
458
            const char * key;
 
459
            GSList * l;
 
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 )
 
464
            {
 
465
                tr_bool changed;
 
466
                tr_benc * oldval = tr_bencDictFind( oldvals, key );
 
467
                if( !oldval )
 
468
                    changed = TRUE;
 
469
                else {
 
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;
 
473
                    tr_free( b );
 
474
                    tr_free( a );
 
475
                }
 
476
 
 
477
                if( changed )
 
478
                    changed_keys = g_slist_append( changed_keys, (gpointer)key );
 
479
            }
 
480
            tr_sessionGetSettings( session, oldvals );
 
481
 
 
482
            for( l=changed_keys; l!=NULL; l=l->next )
 
483
                prefschanged( cbdata->core, key, cbdata );
 
484
 
 
485
            g_slist_free( changed_keys );
 
486
            tr_bencFree( &tmp );
 
487
            break;
 
488
        }
 
489
 
 
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 */
 
495
            break;
 
496
    }
 
497
 
 
498
    gdk_threads_leave( );
 
499
    return status;
 
500
}
 
501
 
 
502
static GSList *
 
503
checkfilenames( int argc, char **argv )
 
504
{
 
505
    int i;
 
506
    GSList * ret = NULL;
 
507
    char * pwd = g_get_current_dir( );
 
508
 
 
509
    for( i=0; i<argc; ++i )
 
510
    {
 
511
        if( gtr_is_supported_url( argv[i] ) || gtr_is_magnet_link( argv[i] ) )
 
512
        {
 
513
            ret = g_slist_prepend( ret, g_strdup( argv[i] ) );
 
514
        }
 
515
        else /* local file */
 
516
        {
 
517
            char * filename = g_path_is_absolute( argv[i] )
 
518
                            ? g_strdup ( argv[i] )
 
519
                            : g_build_filename( pwd, argv[i], NULL );
 
520
 
 
521
            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
 
522
                ret = g_slist_prepend( ret, filename );
 
523
            else {
 
524
                if( gtr_is_hex_hashcode( argv[i] ) )
 
525
                    ret = g_slist_prepend( ret, g_strdup_printf( "magnet:?xt=urn:btih:%s", argv[i] ) );
 
526
                g_free( filename );
 
527
            }
 
528
        }
 
529
    }
 
530
 
 
531
    g_free( pwd );
 
532
    return g_slist_reverse( ret );
 
533
}
 
534
 
 
535
int
 
536
main( int argc, char ** argv )
 
537
{
 
538
    char * err = NULL;
 
539
    GSList * argfiles;
 
540
    GError * gerr;
 
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;
 
549
 
 
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,
 
557
          &startminimized,
 
558
          _( "Start minimized in notification area" ), NULL },
 
559
#endif
 
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 }
 
563
    };
 
564
 
 
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) );
 
574
 
 
575
    /* initialize gtk */
 
576
    if( !g_thread_supported( ) )
 
577
        g_thread_init( NULL );
 
578
 
 
579
    gerr = NULL;
 
580
    if( !gtk_init_with_args( &argc, &argv, (char*)_( "[torrent files or urls]" ), entries,
 
581
                             (char*)domain, &gerr ) )
 
582
    {
 
583
        fprintf( stderr, "%s\n", gerr->message );
 
584
        g_clear_error( &gerr );
 
585
        return 0;
 
586
    }
 
587
 
 
588
    if( showversion )
 
589
    {
 
590
        fprintf( stderr, "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING );
 
591
        return 0;
 
592
    }
 
593
 
 
594
    if( configDir == NULL )
 
595
        configDir = (char*) tr_getDefaultConfigDir( MY_CONFIG_NAME );
 
596
 
 
597
    tr_notify_init( );
 
598
    didinit = cf_init( configDir, NULL ); /* must come before actions_init */
 
599
 
 
600
    setupsighandlers( ); /* set up handlers for fatal signals */
 
601
 
 
602
    didlock = cf_lock( &tr_state, &err );
 
603
    argfiles = checkfilenames( argc - 1, argv + 1 );
 
604
 
 
605
    if( !didlock && argfiles )
 
606
    {
 
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. */
 
611
        GSList * l;
 
612
        gboolean delegated = FALSE;
 
613
        const gboolean trash_originals = pref_flag_get( TR_PREFS_KEY_TRASH_ORIGINAL );
 
614
 
 
615
        for( l=argfiles; l!=NULL; l=l->next )
 
616
        {
 
617
            const char * filename = l->data;
 
618
            const gboolean added = gtr_dbus_add_torrent( filename );
 
619
 
 
620
            if( added && trash_originals )
 
621
                gtr_file_trash_or_remove( filename );
 
622
 
 
623
            delegated |= added;
 
624
        }
 
625
 
 
626
        if( delegated ) {
 
627
            g_slist_foreach( argfiles, (GFunc)g_free, NULL );
 
628
            g_slist_free( argfiles );
 
629
            argfiles = NULL;
 
630
 
 
631
            if( err ) {
 
632
                g_free( err );
 
633
                err = NULL;
 
634
            }
 
635
        }
 
636
    }
 
637
    else if( ( !didlock ) && ( tr_state == GTR_LOCKFILE_ELOCK ) )
 
638
    {
 
639
        /* There's already another copy of Transmission running,
 
640
         * so tell it to present its window to the user */
 
641
        err = NULL;
 
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." ) );
 
644
    }
 
645
 
 
646
    if( didlock && ( didinit || cf_init( configDir, &err ) ) )
 
647
    {
 
648
        /* No other copy of Transmission running...
 
649
         * so we're going to be the primary. */
 
650
 
 
651
        const char * str;
 
652
        GtkWindow * win;
 
653
        GtkUIManager * myUIManager;
 
654
        tr_session * session;
 
655
        struct cbdata * cbdata = g_new0( struct cbdata, 1 );
 
656
 
 
657
        sighandler_cbdata = cbdata;
 
658
 
 
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 );
 
664
 
 
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 );
 
670
 
 
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 );
 
675
#ifdef HAVE_LPI
 
676
        launchpad_integration_add_ui ( myUIManager,
 
677
                                        "/main-window-menu/help-menu/launchpad-items" );
 
678
#endif
 
679
        gtk_ui_manager_ensure_update ( myUIManager );
 
680
        gtk_window_set_default_icon_name ( MY_CONFIG_NAME );
 
681
 
 
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 );
 
685
 
 
686
        appsetup( win, argfiles, cbdata, startpaused, startminimized );
 
687
        tr_sessionSetRPCCallback( session, onRPCChanged, cbdata );
 
688
 
 
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 );
 
697
            }
 
698
        }
 
699
 
 
700
        /* if there's no magnet link handler registered, register us */
 
701
        registerMagnetLinkHandler( );
 
702
 
 
703
        gtk_main( );
 
704
    }
 
705
    else if( err )
 
706
    {
 
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 );
 
712
        g_free( err );
 
713
        gtk_main( );
 
714
    }
 
715
 
 
716
    return 0;
 
717
}
 
718
 
 
719
static void
 
720
onCoreBusy( TrCore * core UNUSED, gboolean busy, struct cbdata * c )
 
721
{
 
722
    tr_window_set_busy( c->wind, busy );
 
723
}
 
724
 
 
725
static void
 
726
appsetup( TrWindow *      wind,
 
727
          GSList *        torrentFiles,
 
728
          struct cbdata * cbdata,
 
729
          gboolean        forcepause,
 
730
          gboolean        isIconified )
 
731
{
 
732
    const pref_flag_t start =
 
733
        forcepause ? PREF_FLAG_FALSE : PREF_FLAG_DEFAULT;
 
734
    const pref_flag_t prompt = PREF_FLAG_DEFAULT;
 
735
 
 
736
    /* fill out cbdata */
 
737
    cbdata->wind         = NULL;
 
738
    cbdata->icon         = NULL;
 
739
    cbdata->msgwin       = NULL;
 
740
    cbdata->prefs        = NULL;
 
741
    cbdata->timer        = 0;
 
742
    cbdata->isClosing    = 0;
 
743
    cbdata->errqueue     = NULL;
 
744
    cbdata->dupqueue     = NULL;
 
745
    cbdata->isIconified  = isIconified;
 
746
 
 
747
    if( isIconified )
 
748
        pref_flag_set( PREF_KEY_SHOW_TRAY_ICON, TRUE );
 
749
 
 
750
    actions_set_core( cbdata->core );
 
751
 
 
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 );
 
758
 
 
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 );
 
762
    torrentFiles = NULL;
 
763
    tr_core_torrents_added( cbdata->core );
 
764
 
 
765
    /* set up main window */
 
766
    winsetup( cbdata, wind );
 
767
 
 
768
    /* set up the icon */
 
769
    prefschanged( cbdata->core, PREF_KEY_SHOW_TRAY_ICON, cbdata );
 
770
 
 
771
    /* start model update timer */
 
772
    cbdata->timer = gtr_timeout_add_seconds( MAIN_WINDOW_REFRESH_INTERVAL_SECONDS, updatemodel, cbdata );
 
773
    updatemodel( cbdata );
 
774
 
 
775
    /* either show the window or iconify it */
 
776
    if( !isIconified )
 
777
        gtk_widget_show( GTK_WIDGET( wind ) );
 
778
    else
 
779
    {
 
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 );
 
784
    }
 
785
 
 
786
    if( !pref_flag_get( PREF_KEY_USER_HAS_GIVEN_INFORMED_CONSENT ) )
 
787
    {
 
788
        GtkWidget * w = gtk_message_dialog_new( GTK_WINDOW( wind ),
 
789
                                                GTK_DIALOG_DESTROY_WITH_PARENT,
 
790
                                                GTK_MESSAGE_INFO,
 
791
                                                GTK_BUTTONS_NONE,
 
792
                                                "%s",
 
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 );
 
802
                break;
 
803
            default:
 
804
                exit( 0 );
 
805
        }
 
806
    }
 
807
}
 
808
 
 
809
static void
 
810
tr_window_present( GtkWindow * window )
 
811
{
 
812
#if GTK_CHECK_VERSION( 2, 8, 0 )
 
813
    gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
 
814
#else
 
815
    gtk_window_present( window );
 
816
#endif
 
817
}
 
818
 
 
819
static void
 
820
toggleMainWindow( struct cbdata * cbdata )
 
821
{
 
822
    GtkWindow * window = GTK_WINDOW( cbdata->wind );
 
823
    const int   doShow = cbdata->isIconified;
 
824
    static int  x = 0;
 
825
    static int  y = 0;
 
826
 
 
827
    if( doShow )
 
828
    {
 
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 );
 
834
    }
 
835
    else
 
836
    {
 
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;
 
841
    }
 
842
}
 
843
 
 
844
static gboolean
 
845
shouldConfirmBeforeExiting( struct cbdata * data )
 
846
{
 
847
    if( !pref_flag_get( PREF_KEY_ASKQUIT ) )
 
848
        return FALSE;
 
849
    else {
 
850
        struct counts_data counts;
 
851
        getTorrentCounts( data, &counts );
 
852
        return counts.activeCount > 0;
 
853
    }
 
854
}
 
855
 
 
856
static void
 
857
maybeaskquit( struct cbdata * cbdata )
 
858
{
 
859
    if( !shouldConfirmBeforeExiting( cbdata ) )
 
860
        wannaquit( cbdata );
 
861
    else {
 
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 ) );
 
865
    }
 
866
}
 
867
 
 
868
static gboolean
 
869
winclose( GtkWidget * w    UNUSED,
 
870
          GdkEvent * event UNUSED,
 
871
          gpointer         gdata )
 
872
{
 
873
    struct cbdata * cbdata = gdata;
 
874
 
 
875
    if( cbdata->icon != NULL )
 
876
        action_activate ( "toggle-main-window" );
 
877
    else
 
878
        maybeaskquit( cbdata );
 
879
 
 
880
    return TRUE; /* don't propagate event further */
 
881
}
 
882
 
 
883
static void
 
884
rowChangedCB( GtkTreeModel  * model UNUSED,
 
885
              GtkTreePath   * path,
 
886
              GtkTreeIter   * iter  UNUSED,
 
887
              gpointer        gdata )
 
888
{
 
889
    struct cbdata * data = gdata;
 
890
    if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
 
891
        refreshActions( gdata );
 
892
}
 
893
 
 
894
static void
 
895
on_drag_data_received( GtkWidget         * widget          UNUSED,
 
896
                       GdkDragContext    * drag_context,
 
897
                       gint                x               UNUSED,
 
898
                       gint                y               UNUSED,
 
899
                       GtkSelectionData  * selection_data,
 
900
                       guint               info            UNUSED,
 
901
                       guint               time_,
 
902
                       gpointer            gdata )
 
903
{
 
904
    int i;
 
905
    gboolean success = FALSE;
 
906
    GSList * filenames = NULL;
 
907
    struct cbdata * data = gdata;
 
908
    char ** uris = gtk_selection_data_get_uris( selection_data );
 
909
 
 
910
    /* try to add the filename URIs... */
 
911
    for( i=0; uris && uris[i]; ++i )
 
912
    {
 
913
        const char * uri = uris[i];
 
914
        char * filename = g_filename_from_uri( uri, NULL, NULL );
 
915
 
 
916
        if( filename && g_file_test( filename, G_FILE_TEST_EXISTS ) )
 
917
        {
 
918
            filenames = g_slist_append( filenames, g_strdup( filename ) );
 
919
            success = TRUE;
 
920
        }
 
921
        else if( tr_urlIsValid( uri, -1 ) || gtr_is_magnet_link( uri ) )
 
922
        {
 
923
            tr_core_add_from_url( data->core, uri );
 
924
            success = TRUE;
 
925
        }
 
926
    }
 
927
 
 
928
    if( filenames )
 
929
        tr_core_add_list_defaults( data->core, g_slist_reverse( filenames ), TRUE );
 
930
 
 
931
    tr_core_torrents_added( data->core );
 
932
    gtk_drag_finish( drag_context, success, FALSE, time_ );
 
933
 
 
934
    /* cleanup */
 
935
    g_strfreev( uris );
 
936
}
 
937
 
 
938
static void
 
939
winsetup( struct cbdata * cbdata, TrWindow * wind )
 
940
{
 
941
    GtkWidget * w;
 
942
    GtkTreeModel * model;
 
943
    GtkTreeSelection * sel;
 
944
 
 
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 ) );
 
948
 
 
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 );
 
955
 
 
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 );
 
961
}
 
962
 
 
963
static gboolean
 
964
onSessionClosed( gpointer gdata )
 
965
{
 
966
    struct cbdata * cbdata = gdata;
 
967
 
 
968
    /* shutdown the gui */
 
969
    while( cbdata->details != NULL ) {
 
970
        struct DetailsDialogHandle * h = cbdata->details->data;
 
971
        gtk_widget_destroy( h->dialog );
 
972
    }
 
973
 
 
974
    if( cbdata->prefs )
 
975
        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
 
976
    if( cbdata->wind )
 
977
        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
 
978
    g_object_unref( cbdata->core );
 
979
    if( cbdata->icon )
 
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 );
 
984
    }
 
985
    if( cbdata->dupqueue ) {
 
986
        g_slist_foreach( cbdata->dupqueue, (GFunc)g_free, NULL );
 
987
        g_slist_free( cbdata->dupqueue );
 
988
    }
 
989
    g_free( cbdata );
 
990
 
 
991
    gtk_main_quit( );
 
992
 
 
993
    return FALSE;
 
994
}
 
995
 
 
996
static gpointer
 
997
sessionCloseThreadFunc( gpointer gdata )
 
998
{
 
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( );
 
1007
    return NULL;
 
1008
}
 
1009
 
 
1010
static void
 
1011
do_exit_cb( GtkWidget *w  UNUSED,
 
1012
            gpointer data UNUSED )
 
1013
{
 
1014
    exit( 0 );
 
1015
}
 
1016
 
 
1017
static void
 
1018
wannaquit( gpointer vdata )
 
1019
{
 
1020
    GtkWidget *r, *p, *b, *w, *c;
 
1021
    struct cbdata *cbdata = vdata;
 
1022
 
 
1023
    /* stop the update timer */
 
1024
    if( cbdata->timer )
 
1025
    {
 
1026
        g_source_remove( cbdata->timer );
 
1027
        cbdata->timer = 0;
 
1028
    }
 
1029
 
 
1030
    c = GTK_WIDGET( cbdata->wind );
 
1031
    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
 
1032
 
 
1033
    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
 
1034
    gtk_container_add( GTK_CONTAINER( c ), r );
 
1035
 
 
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 );
 
1039
 
 
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 );
 
1042
 
 
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 );
 
1047
 
 
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 );
 
1051
 
 
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 );
 
1057
 
 
1058
    gtk_widget_show_all( r );
 
1059
    gtk_widget_grab_focus( w );
 
1060
 
 
1061
    /* clear the UI */
 
1062
    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
 
1063
 
 
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 ) );
 
1071
 
 
1072
    /* shut down libT */
 
1073
    g_thread_create( sessionCloseThreadFunc, vdata, TRUE, NULL );
 
1074
}
 
1075
 
 
1076
static void
 
1077
flushAddTorrentErrors( GtkWindow *  window,
 
1078
                       const char * primary,
 
1079
                       GSList **    files )
 
1080
{
 
1081
    GString *   s = g_string_new( NULL );
 
1082
    GSList *    l;
 
1083
    GtkWidget * w;
 
1084
 
 
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 );
 
1088
    } else {
 
1089
        for( l=*files; l!=NULL; l=l->next )
 
1090
            g_string_append_printf( s, "%s\n", (const char*)l->data );
 
1091
    }
 
1092
    w = gtk_message_dialog_new( window,
 
1093
                                GTK_DIALOG_DESTROY_WITH_PARENT,
 
1094
                                GTK_MESSAGE_ERROR,
 
1095
                                GTK_BUTTONS_CLOSE,
 
1096
                                "%s", primary );
 
1097
    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
 
1098
                                              "%s", s->str );
 
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 );
 
1103
 
 
1104
    g_slist_foreach( *files, (GFunc)g_free, NULL );
 
1105
    g_slist_free( *files );
 
1106
    *files = NULL;
 
1107
}
 
1108
 
 
1109
static void
 
1110
showTorrentErrors( struct cbdata * cbdata )
 
1111
{
 
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 );
 
1118
 
 
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 );
 
1125
}
 
1126
 
 
1127
static void
 
1128
coreerr( TrCore * core UNUSED, guint code, const char * msg, struct cbdata * c )
 
1129
{
 
1130
    switch( code )
 
1131
    {
 
1132
        case TR_PARSE_ERR:
 
1133
            c->errqueue =
 
1134
                g_slist_append( c->errqueue, g_path_get_basename( msg ) );
 
1135
            break;
 
1136
 
 
1137
        case TR_PARSE_DUPLICATE:
 
1138
            c->dupqueue = g_slist_append( c->dupqueue, g_strdup( msg ) );
 
1139
            break;
 
1140
 
 
1141
        case TR_CORE_ERR_NO_MORE_TORRENTS:
 
1142
            showTorrentErrors( c );
 
1143
            break;
 
1144
 
 
1145
        default:
 
1146
            g_assert_not_reached( );
 
1147
            break;
 
1148
    }
 
1149
}
 
1150
 
 
1151
#if GTK_CHECK_VERSION( 2, 8, 0 )
 
1152
static void
 
1153
on_main_window_focus_in( GtkWidget      * widget UNUSED,
 
1154
                         GdkEventFocus  * event  UNUSED,
 
1155
                         gpointer                gdata )
 
1156
{
 
1157
    struct cbdata * cbdata = gdata;
 
1158
 
 
1159
    if( cbdata->wind )
 
1160
        gtk_window_set_urgency_hint( GTK_WINDOW( cbdata->wind ), FALSE );
 
1161
}
 
1162
 
 
1163
#endif
 
1164
 
 
1165
static void
 
1166
onAddTorrent( TrCore *  core,
 
1167
              tr_ctor * ctor,
 
1168
              gpointer  gdata )
 
1169
{
 
1170
    struct cbdata * cbdata = gdata;
 
1171
    GtkWidget *     w = addSingleTorrentDialog( cbdata->wind, core, ctor );
 
1172
 
 
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 );
 
1176
    if( cbdata->wind )
 
1177
        gtk_window_set_urgency_hint( cbdata->wind, TRUE );
 
1178
#endif
 
1179
}
 
1180
 
 
1181
static void
 
1182
prefschanged( TrCore * core UNUSED, const char * key, gpointer data )
 
1183
{
 
1184
    struct cbdata * cbdata = data;
 
1185
    tr_session * tr = tr_core_session( cbdata->core );
 
1186
 
 
1187
    if( !strcmp( key, TR_PREFS_KEY_ENCRYPTION ) )
 
1188
    {
 
1189
        tr_sessionSetEncryption( tr, pref_int_get( key ) );
 
1190
    }
 
1191
    else if( !strcmp( key, TR_PREFS_KEY_DOWNLOAD_DIR ) )
 
1192
    {
 
1193
        tr_sessionSetDownloadDir( tr, pref_string_get( key ) );
 
1194
    }
 
1195
    else if( !strcmp( key, TR_PREFS_KEY_MSGLEVEL ) )
 
1196
    {
 
1197
        tr_setMessageLevel( pref_int_get( key ) );
 
1198
    }
 
1199
    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
 
1200
    {
 
1201
        tr_sessionSetPeerPort( tr, pref_int_get( key ) );
 
1202
    }
 
1203
    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_ENABLED ) )
 
1204
    {
 
1205
        tr_blocklistSetEnabled( tr, pref_flag_get( key ) );
 
1206
    }
 
1207
    else if( !strcmp( key, TR_PREFS_KEY_BLOCKLIST_URL ) )
 
1208
    {
 
1209
        tr_blocklistSetURL( tr, pref_string_get( key ) );
 
1210
    }
 
1211
    else if( !strcmp( key, PREF_KEY_SHOW_TRAY_ICON ) )
 
1212
    {
 
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;
 
1219
        }
 
1220
    }
 
1221
    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_ENABLED ) )
 
1222
    {
 
1223
        tr_sessionLimitSpeed( tr, TR_DOWN, pref_flag_get( key ) );
 
1224
    }
 
1225
    else if( !strcmp( key, TR_PREFS_KEY_DSPEED_KBps ) )
 
1226
    {
 
1227
        tr_sessionSetSpeedLimit_KBps( tr, TR_DOWN, pref_int_get( key ) );
 
1228
    }
 
1229
    else if( !strcmp( key, TR_PREFS_KEY_USPEED_ENABLED ) )
 
1230
    {
 
1231
        tr_sessionLimitSpeed( tr, TR_UP, pref_flag_get( key ) );
 
1232
    }
 
1233
    else if( !strcmp( key, TR_PREFS_KEY_USPEED_KBps ) )
 
1234
    {
 
1235
        tr_sessionSetSpeedLimit_KBps( tr, TR_UP, pref_int_get( key ) );
 
1236
    }
 
1237
    else if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) )
 
1238
    {
 
1239
        tr_sessionSetRatioLimited( tr, pref_flag_get( key ) );
 
1240
    }
 
1241
    else if( !strcmp( key, TR_PREFS_KEY_RATIO ) )
 
1242
    {
 
1243
        tr_sessionSetRatioLimit( tr, pref_double_get( key ) );
 
1244
    }
 
1245
    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT ) )
 
1246
    {
 
1247
        tr_sessionSetIdleLimit( tr, pref_int_get( key ) );
 
1248
    }
 
1249
    else if( !strcmp( key, TR_PREFS_KEY_IDLE_LIMIT_ENABLED ) )
 
1250
    {
 
1251
        tr_sessionSetIdleLimited( tr, pref_flag_get( key ) );
 
1252
    }
 
1253
    else if( !strcmp( key, TR_PREFS_KEY_PORT_FORWARDING ) )
 
1254
    {
 
1255
        tr_sessionSetPortForwardingEnabled( tr, pref_flag_get( key ) );
 
1256
    }
 
1257
    else if( !strcmp( key, TR_PREFS_KEY_PEX_ENABLED ) )
 
1258
    {
 
1259
        tr_sessionSetPexEnabled( tr, pref_flag_get( key ) );
 
1260
    }
 
1261
    else if( !strcmp( key, TR_PREFS_KEY_RENAME_PARTIAL_FILES ) )
 
1262
    {
 
1263
        tr_sessionSetIncompleteFileNamingEnabled( tr, pref_flag_get( key ) );
 
1264
    }
 
1265
    else if( !strcmp( key, TR_PREFS_KEY_DHT_ENABLED ) )
 
1266
    {
 
1267
        tr_sessionSetDHTEnabled( tr, pref_flag_get( key ) );
 
1268
    }
 
1269
    else if( !strcmp( key, TR_PREFS_KEY_LPD_ENABLED ) )
 
1270
    {
 
1271
        tr_sessionSetLPDEnabled( tr, pref_flag_get( key ) );
 
1272
    }
 
1273
    else if( !strcmp( key, TR_PREFS_KEY_RPC_PORT ) )
 
1274
    {
 
1275
        tr_sessionSetRPCPort( tr, pref_int_get( key ) );
 
1276
    }
 
1277
    else if( !strcmp( key, TR_PREFS_KEY_RPC_ENABLED ) )
 
1278
    {
 
1279
        tr_sessionSetRPCEnabled( tr, pref_flag_get( key ) );
 
1280
    }
 
1281
    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST ) )
 
1282
    {
 
1283
        tr_sessionSetRPCWhitelist( tr, pref_string_get( key ) );
 
1284
    }
 
1285
    else if( !strcmp( key, TR_PREFS_KEY_RPC_WHITELIST_ENABLED ) )
 
1286
    {
 
1287
        tr_sessionSetRPCWhitelistEnabled( tr, pref_flag_get( key ) );
 
1288
    }
 
1289
    else if( !strcmp( key, TR_PREFS_KEY_RPC_USERNAME ) )
 
1290
    {
 
1291
        tr_sessionSetRPCUsername( tr, pref_string_get( key ) );
 
1292
    }
 
1293
    else if( !strcmp( key, TR_PREFS_KEY_RPC_PASSWORD ) )
 
1294
    {
 
1295
        tr_sessionSetRPCPassword( tr, pref_string_get( key ) );
 
1296
    }
 
1297
    else if( !strcmp( key, TR_PREFS_KEY_RPC_AUTH_REQUIRED ) )
 
1298
    {
 
1299
        tr_sessionSetRPCPasswordEnabled( tr, pref_flag_get( key ) );
 
1300
    }
 
1301
    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP_KBps ) )
 
1302
    {
 
1303
        tr_sessionSetAltSpeed_KBps( tr, TR_UP, pref_int_get( key ) );
 
1304
    }
 
1305
    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps ) )
 
1306
    {
 
1307
        tr_sessionSetAltSpeed_KBps( tr, TR_DOWN, pref_int_get( key ) );
 
1308
    }
 
1309
    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
 
1310
    {
 
1311
        const gboolean b = pref_flag_get( key );
 
1312
        tr_sessionUseAltSpeed( tr, b );
 
1313
        action_toggle( key, b );
 
1314
    }
 
1315
    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN ) )
 
1316
    {
 
1317
        tr_sessionSetAltSpeedBegin( tr, pref_int_get( key ) );
 
1318
    }
 
1319
    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_END ) )
 
1320
    {
 
1321
        tr_sessionSetAltSpeedEnd( tr, pref_int_get( key ) );
 
1322
    }
 
1323
    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED ) )
 
1324
    {
 
1325
        tr_sessionUseAltSpeedTime( tr, pref_flag_get( key ) );
 
1326
    }
 
1327
    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_TIME_DAY ) )
 
1328
    {
 
1329
        tr_sessionSetAltSpeedDay( tr, pref_int_get( key ) );
 
1330
    }
 
1331
    else if( !strcmp( key, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START ) )
 
1332
    {
 
1333
        tr_sessionSetPeerPortRandomOnStart( tr, pref_flag_get( key ) );
 
1334
    }
 
1335
    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR ) )
 
1336
    {
 
1337
        tr_sessionSetIncompleteDir( tr, pref_string_get( key ) );
 
1338
    }
 
1339
    else if( !strcmp( key, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) )
 
1340
    {
 
1341
        tr_sessionSetIncompleteDirEnabled( tr, pref_flag_get( key ) );
 
1342
    }
 
1343
    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) )
 
1344
    {
 
1345
        tr_sessionSetTorrentDoneScriptEnabled( tr, pref_flag_get( key ) );
 
1346
    }
 
1347
    else if( !strcmp( key, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME ) )
 
1348
    {
 
1349
        tr_sessionSetTorrentDoneScript( tr, pref_string_get( key ) );
 
1350
    }
 
1351
    else if( !strcmp( key, TR_PREFS_KEY_START) )
 
1352
    {
 
1353
        tr_sessionSetPaused( tr, !pref_flag_get( key ) );
 
1354
    }
 
1355
    else if( !strcmp( key, TR_PREFS_KEY_TRASH_ORIGINAL ) )
 
1356
    {
 
1357
        tr_sessionSetDeleteSource( tr, pref_flag_get( key ) );
 
1358
    }
 
1359
}
 
1360
 
 
1361
static gboolean
 
1362
updatemodel( gpointer gdata )
 
1363
{
 
1364
    struct cbdata *data = gdata;
 
1365
    const gboolean done = data->isClosing || global_sigcount;
 
1366
 
 
1367
    if( !done )
 
1368
    {
 
1369
        /* update the torrent data in the model */
 
1370
        tr_core_update( data->core );
 
1371
 
 
1372
        /* update the main window's statusbar and toolbar buttons */
 
1373
        if( data->wind != NULL )
 
1374
            tr_window_update( data->wind );
 
1375
 
 
1376
        /* update the actions */
 
1377
        refreshActions( data );
 
1378
 
 
1379
        /* update the status tray icon */
 
1380
        if( data->icon != NULL )
 
1381
            tr_icon_refresh( data->icon );
 
1382
    }
 
1383
 
 
1384
    return !done;
 
1385
}
 
1386
 
 
1387
static void
 
1388
onUriClicked( GtkAboutDialog * u UNUSED, const gchar * uri, gpointer u2 UNUSED )
 
1389
{
 
1390
    gtr_open_uri( uri );
 
1391
}
 
1392
 
 
1393
static void
 
1394
about( GtkWindow * parent )
 
1395
{
 
1396
    GtkWidget * d;
 
1397
    const char * website_uri = "http://www.transmissionbt.com/";
 
1398
    const char * authors[] = {
 
1399
        "Charles Kerr (Backend; GTK+)",
 
1400
        "Mitchell Livingston (Backend; OS X)",
 
1401
        NULL
 
1402
    };
 
1403
 
 
1404
    gtk_about_dialog_set_url_hook( onUriClicked, NULL, NULL );
 
1405
 
 
1406
    d = g_object_new( GTK_TYPE_ABOUT_DIALOG,
 
1407
                      "authors", authors,
 
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"
 
1414
                         dialog */
 
1415
                      "translator-credits", _( "translator-credits" ),
 
1416
                      "version", LONG_VERSION_STRING,
 
1417
                      "website", website_uri,
 
1418
                      "website-label", website_uri,
 
1419
#ifdef SHOW_LICENSE
 
1420
                      "license", LICENSE,
 
1421
                      "wrap-license", TRUE,
 
1422
#endif
 
1423
                      NULL );
 
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 );
 
1427
}
 
1428
 
 
1429
static void
 
1430
appendIdToBencList( GtkTreeModel * m, GtkTreePath * path UNUSED,
 
1431
                    GtkTreeIter * iter, gpointer list )
 
1432
{
 
1433
    tr_torrent * tor = NULL;
 
1434
    gtk_tree_model_get( m, iter, MC_TORRENT_RAW, &tor, -1 );
 
1435
    tr_bencListAddInt( list, tr_torrentId( tor ) );
 
1436
}
 
1437
 
 
1438
static gboolean
 
1439
rpcOnSelectedTorrents( struct cbdata * data, const char * method )
 
1440
{
 
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 );
 
1445
 
 
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 );
 
1451
 
 
1452
    if( tr_bencListSize( ids ) != 0 )
 
1453
    {
 
1454
        int json_len;
 
1455
        char * json = tr_bencToStr( &top, TR_FMT_JSON_LEAN, &json_len );
 
1456
        tr_rpc_request_exec_json( session, json, json_len, NULL, NULL );
 
1457
        g_free( json );
 
1458
        invoked = TRUE;
 
1459
    }
 
1460
 
 
1461
    tr_bencFree( &top );
 
1462
    return invoked;
 
1463
}
 
1464
 
 
1465
static void
 
1466
openFolderForeach( GtkTreeModel *           model,
 
1467
                   GtkTreePath  * path      UNUSED,
 
1468
                   GtkTreeIter *            iter,
 
1469
                   gpointer       user_data UNUSED )
 
1470
{
 
1471
    TrTorrent * gtor = NULL;
 
1472
 
 
1473
    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
 
1474
    tr_torrent_open_folder( gtor );
 
1475
    g_object_unref( G_OBJECT( gtor ) );
 
1476
}
 
1477
 
 
1478
static gboolean
 
1479
msgwinclosed( void )
 
1480
{
 
1481
    action_toggle( "toggle-message-log", FALSE );
 
1482
    return FALSE;
 
1483
}
 
1484
 
 
1485
static void
 
1486
accumulateSelectedTorrents( GtkTreeModel * model,
 
1487
                            GtkTreePath  * path UNUSED,
 
1488
                            GtkTreeIter  * iter,
 
1489
                            gpointer       gdata )
 
1490
{
 
1491
    GSList ** data = ( GSList** ) gdata;
 
1492
    TrTorrent * gtor = NULL;
 
1493
 
 
1494
    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
 
1495
    *data = g_slist_prepend( *data, gtor );
 
1496
    g_object_unref( G_OBJECT( gtor ) );
 
1497
}
 
1498
 
 
1499
static void
 
1500
removeSelected( struct cbdata * data, gboolean delete_files )
 
1501
{
 
1502
    GSList * l = NULL;
 
1503
    GtkTreeSelection * s = tr_window_get_selection( data->wind );
 
1504
 
 
1505
    gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
 
1506
 
 
1507
    if( l != NULL ) {
 
1508
        l = g_slist_reverse( l );
 
1509
        confirmRemove( data->wind, data->core, l, delete_files );
 
1510
    }
 
1511
}
 
1512
 
 
1513
static void
 
1514
startAllTorrents( struct cbdata * data )
 
1515
{
 
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 );
 
1519
}
 
1520
 
 
1521
static void
 
1522
pauseAllTorrents( struct cbdata * data )
 
1523
{
 
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 );
 
1527
}
 
1528
 
 
1529
static tr_torrent*
 
1530
getFirstSelectedTorrent( struct cbdata * data )
 
1531
{
 
1532
    tr_torrent * tor = NULL;
 
1533
    GtkTreeSelection * s = tr_window_get_selection( data->wind );
 
1534
    GtkTreeModel * m;
 
1535
    GList * l = gtk_tree_selection_get_selected_rows( s, &m );
 
1536
    if( l != NULL ) {
 
1537
        GtkTreePath * p = l->data;
 
1538
        GtkTreeIter i;
 
1539
        if( gtk_tree_model_get_iter( m, &i, p ) )
 
1540
            gtk_tree_model_get( m, &i, MC_TORRENT_RAW, &tor, -1 );
 
1541
    }
 
1542
    g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
 
1543
    g_list_free( l );
 
1544
    return tor;
 
1545
}
 
1546
 
 
1547
static void
 
1548
detailsClosed( gpointer gdata, GObject * dead )
 
1549
{
 
1550
    struct cbdata * data = gdata;
 
1551
    struct DetailsDialogHandle * h = findDetailsDialogFromWidget( data, dead );
 
1552
 
 
1553
    if( h != NULL )
 
1554
    {
 
1555
        data->details = g_slist_remove( data->details, h );
 
1556
        g_free( h->key );
 
1557
        g_free( h );
 
1558
    }
 
1559
}
 
1560
 
 
1561
static void
 
1562
copyMagnetLinkToClipboard( GtkWidget * w, tr_torrent * tor )
 
1563
{
 
1564
    char * magnet = tr_torrentGetMagnetLink( tor );
 
1565
    GdkDisplay * display = gtk_widget_get_display( w );
 
1566
    GdkAtom selection;
 
1567
    GtkClipboard * clipboard;
 
1568
 
 
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 );
 
1573
 
 
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 );
 
1578
 
 
1579
    /* cleanup */
 
1580
    tr_free( magnet );
 
1581
}
 
1582
 
 
1583
void
 
1584
doAction( const char * action_name, gpointer user_data )
 
1585
{
 
1586
    struct cbdata * data = user_data;
 
1587
    gboolean        changed = FALSE;
 
1588
 
 
1589
    if( !strcmp( action_name, "add-torrent-from-url" ) )
 
1590
    {
 
1591
        addURLDialog( data->wind, data->core );
 
1592
    }
 
1593
    else if(  !strcmp( action_name, "add-torrent-menu" )
 
1594
      || !strcmp( action_name, "add-torrent-toolbar" ) )
 
1595
    {
 
1596
        addDialog( data->wind, data->core );
 
1597
    }
 
1598
    else if( !strcmp( action_name, "show-stats" ) )
 
1599
    {
 
1600
        GtkWidget * dialog = stats_dialog_create( data->wind, data->core );
 
1601
        gtk_widget_show( dialog );
 
1602
    }
 
1603
    else if( !strcmp( action_name, "donate" ) )
 
1604
    {
 
1605
        gtr_open_uri( "http://www.transmissionbt.com/donate.php" );
 
1606
    }
 
1607
    else if( !strcmp( action_name, "pause-all-torrents" ) )
 
1608
    {
 
1609
        pauseAllTorrents( data );
 
1610
    }
 
1611
    else if( !strcmp( action_name, "start-all-torrents" ) )
 
1612
    {
 
1613
        startAllTorrents( data );
 
1614
    }
 
1615
    else if( !strcmp( action_name, "copy-magnet-link-to-clipboard" ) )
 
1616
    {
 
1617
        tr_torrent * tor = getFirstSelectedTorrent( data );
 
1618
        if( tor != NULL )
 
1619
        {
 
1620
            copyMagnetLinkToClipboard( GTK_WIDGET( data->wind ), tor );
 
1621
        }
 
1622
    }
 
1623
    else if( !strcmp( action_name, "relocate-torrent" ) )
 
1624
    {
 
1625
        GSList * ids = getSelectedTorrentIds( data );
 
1626
        if( ids != NULL )
 
1627
        {
 
1628
            GtkWindow * parent = GTK_WINDOW( data->wind );
 
1629
            GtkWidget * w = gtr_relocate_dialog_new( parent, data->core, ids );
 
1630
            gtk_widget_show( w );
 
1631
        }
 
1632
    }
 
1633
    else if( !strcmp( action_name, "start-torrent" ) )
 
1634
    {
 
1635
        changed |= rpcOnSelectedTorrents( data, "torrent-start" );
 
1636
    }
 
1637
    else if( !strcmp( action_name, "pause-torrent" ) )
 
1638
    {
 
1639
        changed |= rpcOnSelectedTorrents( data, "torrent-stop" );
 
1640
    }
 
1641
    else if( !strcmp( action_name, "verify-torrent" ) )
 
1642
    {
 
1643
        changed |= rpcOnSelectedTorrents( data, "torrent-verify" );
 
1644
    }
 
1645
    else if( !strcmp( action_name, "update-tracker" ) )
 
1646
    {
 
1647
        changed |= rpcOnSelectedTorrents( data, "torrent-reannounce" );
 
1648
    }
 
1649
    else if( !strcmp( action_name, "open-torrent-folder" ) )
 
1650
    {
 
1651
        GtkTreeSelection * s = tr_window_get_selection( data->wind );
 
1652
        gtk_tree_selection_selected_foreach( s, openFolderForeach, data );
 
1653
    }
 
1654
    else if( !strcmp( action_name, "show-torrent-properties" ) )
 
1655
    {
 
1656
        GtkWidget * w;
 
1657
        GSList * ids = getSelectedTorrentIds( data );
 
1658
        struct DetailsDialogHandle * h = findDetailsDialogFromIds( data, ids );
 
1659
        if( h != NULL )
 
1660
            w = h->dialog;
 
1661
        else {
 
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 );
 
1668
        }
 
1669
        gtk_window_present( GTK_WINDOW( w ) );
 
1670
        g_slist_free( ids );
 
1671
    }
 
1672
    else if( !strcmp( action_name, "new-torrent" ) )
 
1673
    {
 
1674
        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ), data->core );
 
1675
        gtk_widget_show_all( w );
 
1676
    }
 
1677
    else if( !strcmp( action_name, "remove-torrent" ) )
 
1678
    {
 
1679
        removeSelected( data, FALSE );
 
1680
    }
 
1681
    else if( !strcmp( action_name, "delete-torrent" ) )
 
1682
    {
 
1683
        removeSelected( data, TRUE );
 
1684
    }
 
1685
    else if( !strcmp( action_name, "quit" ) )
 
1686
    {
 
1687
        maybeaskquit( data );
 
1688
    }
 
1689
    else if( !strcmp( action_name, "select-all" ) )
 
1690
    {
 
1691
        GtkTreeSelection * s = tr_window_get_selection( data->wind );
 
1692
        gtk_tree_selection_select_all( s );
 
1693
    }
 
1694
    else if( !strcmp( action_name, "deselect-all" ) )
 
1695
    {
 
1696
        GtkTreeSelection * s = tr_window_get_selection( data->wind );
 
1697
        gtk_tree_selection_unselect_all( s );
 
1698
    }
 
1699
    else if( !strcmp( action_name, "edit-preferences" ) )
 
1700
    {
 
1701
        if( NULL == data->prefs )
 
1702
        {
 
1703
            data->prefs = tr_prefs_dialog_new( G_OBJECT( data->core ),
 
1704
                                               data->wind );
 
1705
            g_signal_connect( data->prefs, "destroy",
 
1706
                              G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
 
1707
            gtk_widget_show( GTK_WIDGET( data->prefs ) );
 
1708
        }
 
1709
    }
 
1710
    else if( !strcmp( action_name, "toggle-message-log" ) )
 
1711
    {
 
1712
        if( !data->msgwin )
 
1713
        {
 
1714
            GtkWidget * win = msgwin_new( data->core, data->wind );
 
1715
            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ), NULL );
 
1716
            data->msgwin = win;
 
1717
        }
 
1718
        else
 
1719
        {
 
1720
            action_toggle( "toggle-message-log", FALSE );
 
1721
            gtk_widget_destroy( data->msgwin );
 
1722
            data->msgwin = NULL;
 
1723
        }
 
1724
    }
 
1725
    else if( !strcmp( action_name, "show-about-dialog" ) )
 
1726
    {
 
1727
        about( data->wind );
 
1728
    }
 
1729
    else if( !strcmp ( action_name, "help" ) )
 
1730
    {
 
1731
        gtr_open_uri( gtr_get_help_uri( ) );
 
1732
    }
 
1733
    else if( !strcmp( action_name, "toggle-main-window" ) )
 
1734
    {
 
1735
        toggleMainWindow( data );
 
1736
    }
 
1737
    else g_error ( "Unhandled action: %s", action_name );
 
1738
 
 
1739
    if( changed )
 
1740
        updatemodel( data );
 
1741
}