transmission/gtk/main.c

1132 lines
32 KiB
C
Raw Normal View History

/******************************************************************************
* $Id$
*
* Copyright (c) 2005-2007 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
2006-07-16 19:39:23 +00:00
#include <sys/param.h>
#include <errno.h>
2007-02-19 22:09:05 +00:00
#include <getopt.h>
2006-07-16 19:39:23 +00:00
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include "conf.h"
#include "dialogs.h"
#include "ipc.h"
2006-08-20 22:45:29 +00:00
#include "msgwin.h"
#include "torrent-inspector.h"
#include "tr_cell_renderer_progress.h"
#include "tr_core.h"
2007-02-19 22:09:05 +00:00
#include "tr_icon.h"
#include "tr_prefs.h"
#include "tr_torrent.h"
#include "tr_window.h"
2006-07-16 19:39:23 +00:00
#include "util.h"
2007-02-19 22:09:05 +00:00
#include "transmission.h"
#include "version.h"
2007-02-19 22:09:05 +00:00
2007-02-07 05:08:53 +00:00
#include "img_icon_full.h"
2006-07-16 19:39:23 +00:00
/* time in seconds to wait for torrents to stop when exiting */
#define TRACKER_EXIT_TIMEOUT 10
/* interval in milliseconds to update the torrent list display */
#define UPDATE_INTERVAL 1000
/* interval in milliseconds to check for stopped torrents and update display */
#define EXIT_CHECK_INTERVAL 500
2007-02-19 22:09:05 +00:00
/* number of fatal signals required to cause an immediate exit */
#define SIGCOUNT_MAX 3
static const char * LICENSE =
"The Transmission binaries and source code are distributed under the MIT "
"license. "
"\n\n"
"Permission is hereby granted, free of charge, to any person obtaining "
"a copy of this software and associated documentation files (the "
"'Software'), to deal in the Software without restriction, including "
"without limitation the rights to use, copy, modify, merge, publish, "
"distribute, sublicense, and/or sell copies of the Software, and to "
"permit persons to whom the Software is furnished to do so, subject to "
"the following conditions: "
"\n\n"
"The above copyright notice and this permission notice shall be included "
"in all copies or substantial portions of the Software. "
"\n\n"
"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, "
"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. "
"IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY "
"CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, "
"TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE "
"SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
2006-07-16 19:39:23 +00:00
struct cbdata {
2007-02-19 22:09:05 +00:00
GtkWindow * wind;
TrCore * core;
2007-02-19 22:09:05 +00:00
TrIcon * icon;
TrPrefs * prefs;
guint timer;
gboolean msgwinopen;
gboolean closing;
GList * errqueue;
2006-07-16 19:39:23 +00:00
};
struct exitdata {
2007-02-19 22:09:05 +00:00
struct cbdata * cbdata;
time_t started;
guint timer;
2006-07-16 19:39:23 +00:00
};
enum
{
ACT_OPEN = 0,
ACT_START,
ACT_STOP,
ACT_DELETE,
ACT_SEPARATOR1,
ACT_INFO,
ACT_DEBUG,
ACT_ABOUT,
ACT_SEPARATOR2,
ACT_PREF,
ACT_SEPARATOR3,
ACT_CLOSE,
ACT_QUIT,
2007-02-19 22:09:05 +00:00
ACT_ICON,
ACTION_COUNT,
};
struct
{
const char * label;
const char * icon;
guint key;
int flags;
const char * tooltip;
}
actions[] =
{
{ NULL, GTK_STOCK_ADD, 'o', ACTF_WHEREVER | ACTF_ALWAYS,
N_("Add a new torrent") },
{ N_("Start"), GTK_STOCK_EXECUTE, 0, ACTF_WHEREVER | ACTF_INACTIVE,
N_("Start a torrent that is not running") },
{ NULL, GTK_STOCK_STOP, 0, ACTF_WHEREVER | ACTF_ACTIVE,
N_("Stop a torrent that is running") },
{ NULL, GTK_STOCK_REMOVE, 0, ACTF_WHEREVER | ACTF_WHATEVER,
N_("Remove a torrent") },
{ NULL, NULL, 0, ACTF_SEPARATOR, NULL },
{ NULL, GTK_STOCK_PROPERTIES, 0, ACTF_WHEREVER | ACTF_WHATEVER,
N_("Show additional information about a torrent") },
{ N_("Open debug window"), GTK_STOCK_INFO, 0, ACTF_MENU | ACTF_ALWAYS,
NULL },
{ NULL, GTK_STOCK_ABOUT, 0, ACTF_MENU | ACTF_ALWAYS,
N_("About Transmission") },
{ NULL, NULL, 0, ACTF_SEPARATOR, NULL },
{ NULL, GTK_STOCK_PREFERENCES, 0, ACTF_WHEREVER | ACTF_ALWAYS,
N_("Customize application behavior") },
{ NULL, NULL, 0, ACTF_SEPARATOR, NULL },
{ NULL, GTK_STOCK_CLOSE, 0, ACTF_MENU | ACTF_ALWAYS,
N_("Close the main window") },
{ NULL, GTK_STOCK_QUIT, 0, ACTF_MENU | ACTF_ALWAYS,
N_("Exit the program") },
2007-02-19 22:09:05 +00:00
/* this isn't a terminator for the list, it's ACT_ICON */
{ NULL, NULL, 0, 0, NULL },
};
#define CBDATA_PTR "callback-data-pointer"
static sig_atomic_t global_sigcount = 0;
static GList *
2007-02-19 22:09:05 +00:00
readargs( int argc, char ** argv, gboolean * sendquit, gboolean * paused );
static gboolean
sendremote( GList * files, gboolean sendquit );
static void
gtksetup( int * argc, char *** argv );
static void
appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused );
static void
2007-02-19 22:09:05 +00:00
winsetup( struct cbdata * cbdata, TrWindow * wind );
static void
iconclick( struct cbdata * cbdata );
2007-02-19 22:09:05 +00:00
static void
makeicon( struct cbdata * cbdata );
static gboolean
2007-02-19 22:09:05 +00:00
winclose( GtkWidget * widget, GdkEvent * event, gpointer gdata );
static void
wannaquit( void * vdata );
static gboolean
2006-07-16 19:39:23 +00:00
exitcheck(gpointer gdata);
static void
2006-07-16 19:39:23 +00:00
setupdrag(GtkWidget *widget, struct cbdata *data);
static void
2006-07-16 19:39:23 +00:00
gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
GtkSelectionData *sel, guint info, guint time, gpointer gdata);
2007-02-19 22:09:05 +00:00
static void
coreerr( TrCore * core, enum tr_core_err code, const char * msg,
gpointer gdata );
static void
coreprompt( TrCore *, GList *, enum tr_torrent_action, gboolean, gpointer );
static void
2007-05-24 10:37:07 +00:00
corepromptdata( TrCore *, uint8_t *, size_t, gboolean, gpointer );
static void
2007-02-19 22:09:05 +00:00
readinitialprefs( struct cbdata * cbdata );
static void
prefschanged( TrCore * core, int id, gpointer data );
2007-03-31 19:19:27 +00:00
static void
setpex( tr_torrent_t * tor, void * arg );
static gboolean
2006-07-16 19:39:23 +00:00
updatemodel(gpointer gdata);
static void
2007-02-19 22:09:05 +00:00
boolwindclosed(GtkWidget *widget, gpointer gdata);
static void
windact(GtkWidget *widget, int action, gpointer gdata);
2007-02-19 22:09:05 +00:00
static GList *
getselection( struct cbdata * cbdata );
static void
handleaction( struct cbdata *data, int action );
static void
2006-07-16 19:39:23 +00:00
safepipe(void);
static void
2006-07-16 19:39:23 +00:00
setupsighandlers(void);
static void
2006-07-16 19:39:23 +00:00
fatalsig(int sig);
int
2007-02-19 22:09:05 +00:00
main( int argc, char ** argv )
{
GtkWindow * mainwind;
char * err;
benc_val_t * state;
GList * argfiles;
gboolean didinit, didlock, sendquit, startpaused;
safepipe(); /* ignore SIGPIPE */
argfiles = readargs( argc, argv, &sendquit, &startpaused );
didinit = cf_init( tr_getPrefsDirectory(), NULL );
didlock = FALSE;
if( didinit )
{
/* maybe send remote commands, also try cf_lock() */
didlock = sendremote( argfiles, sendquit );
}
setupsighandlers(); /* set up handlers for fatal signals */
gtksetup( &argc, &argv ); /* set up gtk and gettext */
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
if( ( didinit || cf_init( tr_getPrefsDirectory(), &err ) ) &&
( didlock || cf_lock( &err ) ) )
{
/* create main window now to be a parent to any error dialogs */
mainwind = GTK_WINDOW( tr_window_new() );
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
/* try to load prefs and saved state */
cf_loadprefs( &err );
if( NULL != err )
{
errmsg( mainwind, "%s", err );
g_free( err );
}
state = cf_loadstate( &err );
if( NULL != err )
{
errmsg( mainwind, "%s", err );
g_free( err );
}
2006-08-20 22:45:29 +00:00
2007-02-19 22:09:05 +00:00
msgwin_loadpref(); /* set message level here before tr_init() */
appsetup( TR_WINDOW( mainwind ), state, argfiles, startpaused );
cf_freestate( state );
}
else
{
gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
NULL, "%s", err ) );
g_free( err );
}
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
freestrlist(argfiles);
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
gtk_main();
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
return 0;
}
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
GList *
readargs( int argc, char ** argv, gboolean * sendquit, gboolean * startpaused )
{
struct option opts[] =
{
{ "help", no_argument, NULL, 'h' },
{ "paused", no_argument, NULL, 'p' },
{ "quit", no_argument, NULL, 'q' },
{ "version", no_argument, NULL, 'v' },
{ NULL, 0, NULL, 0 }
};
2007-03-24 09:23:24 +00:00
int opt;
const char * name;
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
*sendquit = FALSE;
*startpaused = FALSE;
gtk_parse_args( &argc, &argv );
2007-03-24 09:23:24 +00:00
name = g_get_prgname();
2007-02-19 22:09:05 +00:00
while( 0 <= ( opt = getopt_long( argc, argv, "hpqv", opts, NULL ) ) )
{
switch( opt )
{
case 'p':
*startpaused = TRUE;
break;
case 'q':
*sendquit = TRUE;
break;
case 'v':
case 'h':
printf(
2007-03-24 09:23:24 +00:00
_("usage: %s [-hpq] [files...]\n"
2007-02-19 22:09:05 +00:00
"\n"
2007-03-24 09:23:24 +00:00
"Transmission %s (r%d) http://transmission.m0k.org/\n"
2007-02-19 22:09:05 +00:00
"A free, lightweight BitTorrent client with a simple, intuitive interface\n"
"\n"
" -h --help display this message and exit\n"
" -p --paused start with all torrents paused\n"
2007-03-24 09:23:24 +00:00
" -q --quit request that the running %s instance quit\n"
2007-02-19 22:09:05 +00:00
"\n"
2007-03-24 09:23:24 +00:00
"Only one instance of %s may run at one time. Multiple\n"
2007-02-19 22:09:05 +00:00
"torrent files may be loaded at startup by adding them to the command\n"
2007-03-24 09:23:24 +00:00
"line. If %s is already running, those torrents will be\n"
2007-02-19 22:09:05 +00:00
"opened in the running instance.\n"),
2007-03-24 09:23:24 +00:00
name, VERSION_STRING, VERSION_REVISION,
name, name, name );
2007-02-19 22:09:05 +00:00
exit(0);
break;
}
}
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
argc -= optind;
argv += optind;
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
return checkfilenames( argc, argv );
2006-07-16 19:39:23 +00:00
}
2007-02-19 22:09:05 +00:00
static gboolean
sendremote( GList * files, gboolean sendquit )
{
gboolean didlock;
didlock = cf_lock( NULL );
if( NULL != files )
{
/* send files if there's another instance, otherwise start normally */
if( !didlock )
{
exit( ipc_sendfiles_blocking( files ) ? 0 : 1 );
}
2006-07-16 19:39:23 +00:00
}
2007-02-19 22:09:05 +00:00
if( sendquit )
{
/* either send a quit message or exit if no other instance */
if( !didlock )
{
exit( ipc_sendquit_blocking() ? 0 : 1 );
}
exit( 0 );
2006-07-16 19:39:23 +00:00
}
2007-02-19 22:09:05 +00:00
return didlock;
2006-07-16 19:39:23 +00:00
}
static void
2007-02-19 22:09:05 +00:00
gtksetup( int * argc, char *** argv )
{
2007-02-19 22:09:05 +00:00
GdkPixbuf * icon;
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
gtk_init( argc, argv );
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
bindtextdomain( "transmission-gtk", LOCALEDIR );
bind_textdomain_codeset( "transmission-gtk", "UTF-8" );
textdomain( "transmission-gtk" );
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
g_set_application_name( _("Transmission") );
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
/* tweak some style properties in dialogs to get closer to the GNOME HiG */
gtk_rc_parse_string(
"style \"transmission-standard\"\n"
"{\n"
" GtkDialog::action-area-border = 6\n"
" GtkDialog::button-spacing = 12\n"
" GtkDialog::content-area-border = 6\n"
"}\n"
"widget \"TransmissionDialog\" style \"transmission-standard\"\n" );
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
icon = gdk_pixbuf_new_from_inline( -1, tr_icon_full, FALSE, NULL );
gtk_window_set_default_icon( icon );
g_object_unref( icon );
}
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
static void
appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused )
{
struct cbdata * cbdata;
enum tr_torrent_action action;
2007-02-19 22:09:05 +00:00
/* fill out cbdata */
cbdata = g_new0( struct cbdata, 1 );
cbdata->wind = NULL;
cbdata->core = tr_core_new();
2007-02-19 22:09:05 +00:00
cbdata->icon = NULL;
cbdata->prefs = NULL;
cbdata->timer = 0;
cbdata->msgwinopen = FALSE;
cbdata->closing = FALSE;
cbdata->errqueue = NULL;
/* set up core handlers */
g_signal_connect( cbdata->core, "error", G_CALLBACK( coreerr ), cbdata );
g_signal_connect( cbdata->core, "directory-prompt",
G_CALLBACK( coreprompt ), cbdata );
2007-05-24 10:37:07 +00:00
g_signal_connect( cbdata->core, "directory-prompt-data",
G_CALLBACK( corepromptdata ), cbdata );
g_signal_connect_swapped( cbdata->core, "quit",
G_CALLBACK( wannaquit ), cbdata );
g_signal_connect( cbdata->core, "prefs-changed",
G_CALLBACK( prefschanged ), cbdata );
2007-02-19 22:09:05 +00:00
/* apply a few prefs */
readinitialprefs( cbdata );
/* add torrents from command-line and saved state */
if( NULL != state )
{
tr_core_load( cbdata->core, state, paused );
}
if( NULL != args )
{
action = toraddaction( tr_prefs_get( PREF_ID_ADDIPC ) );
tr_core_add_list( cbdata->core, args, action, paused );
}
tr_core_torrents_added( cbdata->core );
/* set up the ipc socket */
ipc_socket_setup( GTK_WINDOW( wind ), cbdata->core );
2007-02-19 22:09:05 +00:00
/* set up main window */
winsetup( cbdata, wind );
/* start model update timer */
cbdata->timer = g_timeout_add( UPDATE_INTERVAL, updatemodel, cbdata );
updatemodel( cbdata );
2006-07-16 19:39:23 +00:00
/* show the window */
tr_window_show( wind );
2006-07-16 19:39:23 +00:00
}
static void
2007-02-19 22:09:05 +00:00
winsetup( struct cbdata * cbdata, TrWindow * wind )
{
2007-02-19 22:09:05 +00:00
int ii;
GtkWidget * drag;
g_assert( ACTION_COUNT == ALEN( actions ) );
g_assert( NULL == cbdata->wind );
cbdata->wind = GTK_WINDOW( wind );
for( ii = 0; ii < ALEN( actions ); ii++ )
{
tr_window_action_add( wind, ii, actions[ii].flags,
gettext( actions[ii].label ), actions[ii].icon,
gettext( actions[ii].tooltip ),
actions[ii].key );
2007-02-19 22:09:05 +00:00
}
g_object_set( wind, "model", tr_core_model( cbdata->core ),
2007-02-19 22:09:05 +00:00
"double-click-action", ACT_INFO, NULL);
g_signal_connect( wind, "action", G_CALLBACK( windact ), cbdata );
g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
g_object_get( wind, "drag-widget", &drag, NULL );
setupdrag( drag, cbdata );
}
static void
iconclick( struct cbdata * cbdata )
2007-02-19 22:09:05 +00:00
{
GtkWidget * win;
if( NULL != cbdata->wind )
{
/* close window */
winclose( NULL, NULL, cbdata );
}
else
{
/* create window */
win = tr_window_new();
winsetup( cbdata, TR_WINDOW( win ) );
tr_window_show( TR_WINDOW( win ) );
2006-08-20 22:45:29 +00:00
}
2007-02-19 22:09:05 +00:00
}
static void
makeicon( struct cbdata * cbdata )
{
TrIcon * icon;
int ii;
2007-02-19 22:09:05 +00:00
if( NULL != cbdata->icon )
{
2007-02-19 22:09:05 +00:00
return;
}
2007-02-19 22:09:05 +00:00
icon = tr_icon_new();
for( ii = 0; ii < ALEN( actions ); ii++ )
{
tr_icon_action_add( TR_ICON( icon ), ii, actions[ii].flags,
gettext( actions[ii].label ), actions[ii].icon );
}
2007-02-19 22:09:05 +00:00
g_object_set( icon, "activate-action", ACT_ICON, NULL);
g_signal_connect( icon, "action", G_CALLBACK( windact ), cbdata );
2007-02-19 22:09:05 +00:00
cbdata->icon = icon;
2006-07-16 19:39:23 +00:00
}
2007-02-19 22:09:05 +00:00
static gboolean
winclose( GtkWidget * widget SHUTUP, GdkEvent * event SHUTUP, gpointer gdata )
{
struct cbdata * cbdata;
cbdata = gdata;
if( NULL != cbdata->icon && tr_icon_docked( cbdata->icon ) )
{
gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
cbdata->wind = NULL;
}
else
{
askquit( cbdata->wind, wannaquit, cbdata );
}
/* don't propagate event further */
return TRUE;
}
static void
wannaquit( void * vdata )
{
struct cbdata * data;
2006-07-16 19:39:23 +00:00
struct exitdata *edata;
2007-02-19 22:09:05 +00:00
data = vdata;
if( data->closing )
{
return;
}
2006-07-16 19:39:23 +00:00
data->closing = TRUE;
/* stop the update timer */
if(0 < data->timer)
g_source_remove(data->timer);
data->timer = 0;
/* pause torrents and stop nat traversal */
tr_core_shutdown( data->core );
2006-09-25 18:37:45 +00:00
2006-07-16 19:39:23 +00:00
/* set things up to wait for torrents to stop */
edata = g_new0(struct exitdata, 1);
edata->cbdata = data;
edata->started = time(NULL);
/* check if torrents are still running */
if(exitcheck(edata)) {
/* yes, start the exit timer and disable widgets */
edata->timer = g_timeout_add(EXIT_CHECK_INTERVAL, exitcheck, edata);
2007-02-19 22:09:05 +00:00
if( NULL != data->wind )
{
gtk_widget_set_sensitive( GTK_WIDGET( data->wind ), FALSE );
}
2006-07-16 19:39:23 +00:00
}
}
2007-02-19 22:09:05 +00:00
static gboolean
exitcheck( gpointer gdata )
{
struct exitdata * edata;
struct cbdata * cbdata;
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
edata = gdata;
cbdata = edata->cbdata;
/* keep waiting until we're ready to quit or we hit the exit timeout */
2007-02-19 22:09:05 +00:00
if( time( NULL ) - edata->started < TRACKER_EXIT_TIMEOUT )
{
if( !tr_core_quiescent( cbdata->core ) )
2007-02-19 22:09:05 +00:00
{
updatemodel( cbdata );
return TRUE;
}
}
2006-07-16 19:39:23 +00:00
2007-02-19 22:09:05 +00:00
/* exit otherwise */
if( 0 < edata->timer )
{
g_source_remove( edata->timer );
}
g_free( edata );
/* note that cbdata->prefs holds a reference to cbdata->core, and
it's destruction may trigger callbacks that use cbdata->core */
2007-02-19 22:09:05 +00:00
if( NULL != cbdata->prefs )
{
gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
}
if( NULL != cbdata->wind )
{
gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
}
g_object_unref( cbdata->core );
2007-02-19 22:09:05 +00:00
if( NULL != cbdata->icon )
{
g_object_unref( cbdata->icon );
}
g_assert( 0 == cbdata->timer );
if( NULL != cbdata->errqueue )
{
g_list_foreach( cbdata->errqueue, (GFunc) g_free, NULL );
g_list_free( cbdata->errqueue );
}
2007-02-19 22:09:05 +00:00
g_free( cbdata );
gtk_main_quit();
return FALSE;
2006-07-16 19:39:23 +00:00
}
static void
2006-07-16 19:39:23 +00:00
gotdrag(GtkWidget *widget SHUTUP, GdkDragContext *dc, gint x SHUTUP,
gint y SHUTUP, GtkSelectionData *sel, guint info SHUTUP, guint time,
gpointer gdata) {
struct cbdata *data = gdata;
char prefix[] = "file:";
char *files, *decoded, *deslashed, *hostless;
int ii, len;
GList *errs;
struct stat sb;
int prelen = strlen(prefix);
GList *paths, *freeables;
enum tr_torrent_action action;
2006-07-16 19:39:23 +00:00
#ifdef DND_DEBUG
char *sele = gdk_atom_name(sel->selection);
char *targ = gdk_atom_name(sel->target);
char *type = gdk_atom_name(sel->type);
fprintf(stderr, "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i\n",
sele, targ, type, sel->format, sel->length);
g_free(sele);
g_free(targ);
g_free(type);
if(8 == sel->format) {
for(ii = 0; ii < sel->length; ii++)
fprintf(stderr, "%02X ", sel->data[ii]);
fprintf(stderr, "\n");
}
#endif
errs = NULL;
paths = NULL;
freeables = NULL;
if(gdk_atom_intern("XdndSelection", FALSE) == sel->selection &&
8 == sel->format) {
/* split file list on carriage returns and linefeeds */
files = g_new(char, sel->length + 1);
memcpy(files, sel->data, sel->length);
files[sel->length] = '\0';
for(ii = 0; '\0' != files[ii]; ii++)
if('\015' == files[ii] || '\012' == files[ii])
files[ii] = '\0';
/* try to get a usable filename out of the URI supplied and add it */
for(ii = 0; ii < sel->length; ii += len + 1) {
if('\0' == files[ii])
len = 0;
else {
len = strlen(files + ii);
/* de-urlencode the URI */
decoded = urldecode(files + ii, len);
freeables = g_list_append(freeables, decoded);
if(g_utf8_validate(decoded, -1, NULL)) {
/* remove the file: prefix */
if(prelen < len && 0 == strncmp(prefix, decoded, prelen)) {
deslashed = decoded + prelen;
/* trim excess / characters from the beginning */
while('/' == deslashed[0] && '/' == deslashed[1])
deslashed++;
/* if the file doesn't exist, the first part might be a hostname */
if(0 > g_stat(deslashed, &sb) &&
NULL != (hostless = strchr(deslashed + 1, '/')) &&
0 == g_stat(hostless, &sb))
deslashed = hostless;
/* finally, add it to the list of torrents to try adding */
paths = g_list_append(paths, deslashed);
}
}
}
}
/* try to add any torrents we found */
2007-02-19 22:09:05 +00:00
if( NULL != paths )
{
action = toraddaction( tr_prefs_get( PREF_ID_ADDSTD ) );
tr_core_add_list( data->core, paths, action, FALSE );
tr_core_torrents_added( data->core );
g_list_free(paths);
2007-02-19 22:09:05 +00:00
}
2006-07-16 19:39:23 +00:00
freestrlist(freeables);
g_free(files);
}
gtk_drag_finish(dc, (NULL != paths), FALSE, time);
}
static void
2006-07-16 19:39:23 +00:00
setupdrag(GtkWidget *widget, struct cbdata *data) {
GtkTargetEntry targets[] = {
{ "STRING", 0, 0 },
{ "text/plain", 0, 0 },
{ "text/uri-list", 0, 0 },
};
g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
}
static void
coreerr( TrCore * core SHUTUP, enum tr_core_err code, const char * msg,
gpointer gdata )
{
struct cbdata * cbdata = gdata;
char * joined;
switch( code )
{
case TR_CORE_ERR_ADD_TORRENT:
cbdata->errqueue = g_list_append( cbdata->errqueue,
g_strdup( msg ) );
return;
case TR_CORE_ERR_NO_MORE_TORRENTS:
if( NULL != cbdata->errqueue )
{
joined = joinstrlist( cbdata->errqueue, "\n" );
errmsg( cbdata->wind,
ngettext( "Failed to load torrent file:\n%s",
"Failed to load torrent files:\n%s",
g_list_length( cbdata->errqueue ) ),
joined );
g_list_foreach( cbdata->errqueue, (GFunc) g_free, NULL );
g_list_free( cbdata->errqueue );
cbdata->errqueue = NULL;
g_free( joined );
}
return;
case TR_CORE_ERR_SAVE_STATE:
errmsg( cbdata->wind, "%s", msg );
return;
}
g_assert_not_reached();
}
void
coreprompt( TrCore * core, GList * paths, enum tr_torrent_action act,
gboolean paused, gpointer gdata )
{
struct cbdata * cbdata = gdata;
2007-05-24 10:37:07 +00:00
promptfordir( cbdata->wind, core, paths, NULL, 0, act, paused );
}
void
corepromptdata( TrCore * core, uint8_t * data, size_t size,
gboolean paused, gpointer gdata )
{
struct cbdata * cbdata = gdata;
promptfordir( cbdata->wind, core, NULL, data, size, TR_TOR_LEAVE, paused );
}
2007-02-19 22:09:05 +00:00
static void
readinitialprefs( struct cbdata * cbdata )
{
int prefs[] =
{
PREF_ID_PORT,
PREF_ID_USEDOWNLIMIT,
PREF_ID_USEUPLIMIT,
PREF_ID_NAT,
PREF_ID_ICON,
2007-03-31 19:19:27 +00:00
PREF_ID_PEX,
2007-02-19 22:09:05 +00:00
};
int ii;
for( ii = 0; ALEN( prefs ) > ii; ii++ )
{
prefschanged( NULL, prefs[ii], cbdata );
}
}
static void
prefschanged( TrCore * core SHUTUP, int id, gpointer data )
2007-02-19 22:09:05 +00:00
{
struct cbdata * cbdata;
tr_handle_t * tr;
int num;
2007-03-31 19:19:27 +00:00
gboolean boolval;
2007-02-19 22:09:05 +00:00
cbdata = data;
tr = tr_core_handle( cbdata->core );
2007-02-19 22:09:05 +00:00
switch( id )
{
case PREF_ID_PORT:
tr_setBindPort( tr, tr_prefs_get_int_with_default( id ) );
break;
case PREF_ID_USEDOWNLIMIT:
case PREF_ID_DOWNLIMIT:
num = -1;
if( tr_prefs_get_bool_with_default( PREF_ID_USEDOWNLIMIT ) )
{
num = tr_prefs_get_int_with_default( PREF_ID_DOWNLIMIT );
}
tr_setGlobalDownloadLimit( tr, num );
break;
case PREF_ID_USEUPLIMIT:
case PREF_ID_UPLIMIT:
num = -1;
if( tr_prefs_get_bool_with_default( PREF_ID_USEUPLIMIT ) )
{
num = tr_prefs_get_int_with_default( PREF_ID_UPLIMIT );
}
tr_setGlobalUploadLimit( tr, num );
break;
case PREF_ID_NAT:
tr_natTraversalEnable( tr, tr_prefs_get_bool_with_default( id ) );
break;
case PREF_ID_ICON:
if( tr_prefs_get_bool_with_default( id ) )
{
makeicon( cbdata );
}
else if( NULL != cbdata->icon )
{
g_object_unref( cbdata->icon );
cbdata->icon = NULL;
}
break;
2007-03-31 19:19:27 +00:00
case PREF_ID_PEX:
boolval = tr_prefs_get_bool_with_default( id );
tr_torrentIterate( tr, setpex, &boolval );
break;
2007-02-19 22:09:05 +00:00
case PREF_ID_DIR:
case PREF_ID_ASKDIR:
case PREF_ID_ADDSTD:
case PREF_ID_ADDIPC:
case PREF_ID_MSGLEVEL:
case PREF_MAX_ID:
break;
}
}
2007-03-31 19:19:27 +00:00
void
setpex( tr_torrent_t * tor, void * arg )
{
gboolean * val;
val = arg;
tr_torrentDisablePex( tor, !(*val) );
}
2006-07-16 19:39:23 +00:00
gboolean
updatemodel(gpointer gdata) {
struct cbdata *data = gdata;
float up, down;
2007-02-19 22:09:05 +00:00
if( !data->closing && 0 < global_sigcount )
{
wannaquit( data );
return FALSE;
2006-07-16 19:39:23 +00:00
}
/* update the torrent data in the model */
tr_core_update( data->core );
2006-07-16 19:39:23 +00:00
/* update the main window's statusbar and toolbar buttons */
2007-02-19 22:09:05 +00:00
if( NULL != data->wind )
{
tr_torrentRates( tr_core_handle( data->core ), &down, &up );
2007-02-19 22:09:05 +00:00
tr_window_update( TR_WINDOW( data->wind ), down, up );
}
2006-07-16 19:39:23 +00:00
2006-08-20 22:45:29 +00:00
/* update the message window */
msgwin_update();
2006-07-16 19:39:23 +00:00
return TRUE;
}
static void
2006-08-20 22:45:29 +00:00
boolwindclosed(GtkWidget *widget SHUTUP, gpointer gdata) {
gboolean *preachy_gcc = gdata;
*preachy_gcc = FALSE;
}
static void
windact( GtkWidget * wind SHUTUP, int action, gpointer gdata )
{
g_assert( 0 <= action );
handleaction( gdata, action );
}
2007-02-19 22:09:05 +00:00
/* returns a GList containing a GtkTreeRowReference to each selected row */
static GList *
getselection( struct cbdata * cbdata )
{
GtkTreeSelection * sel;
GList * rows, * ii;
GtkTreeRowReference * ref;
if( NULL == cbdata->wind )
{
return NULL;
}
g_object_get( cbdata->wind, "selection", &sel, NULL );
rows = gtk_tree_selection_get_selected_rows( sel, NULL );
for( ii = rows; NULL != ii; ii = ii->next )
{
ref = gtk_tree_row_reference_new( tr_core_model( cbdata->core ), ii->data );
2007-02-19 22:09:05 +00:00
gtk_tree_path_free( ii->data );
ii->data = ref;
}
return rows;
}
static void
about ( void )
{
char buf[128];
GtkWidget * w = gtk_about_dialog_new ();
GtkAboutDialog * a = GTK_ABOUT_DIALOG (w);
const char *authors[] = { "Eric Petit (Back-end; OS X)",
"Josh Elsasser (Back-end; GTK+)",
"Mitchell Livingston (Back-end; OS X)",
"Charles Kerr (Back-end; GTK+)",
"Bryan Varner (BeOS)",
NULL };
g_snprintf (buf, sizeof(buf), _("%s (%d)"), VERSION_STRING, VERSION_REVISION);
gtk_about_dialog_set_version (a, buf);
gtk_about_dialog_set_license (a, LICENSE);
gtk_about_dialog_set_wrap_license (a, TRUE);
gtk_about_dialog_set_website (a, "http://transmission.m0k.org/");
gtk_about_dialog_set_copyright (a, _("Copyright 2005-2007 The Transmission Project"));
gtk_about_dialog_set_authors (a, authors);
gtk_about_dialog_set_translator_credits (a, _("translator-credits"));
g_signal_connect_swapped (w, "response", G_CALLBACK (gtk_widget_destroy), w);
gtk_widget_show_all (w);
}
static void
handleaction( struct cbdata * data, int act )
{
2006-07-16 19:39:23 +00:00
GList *rows, *ii;
GtkTreeModel * model;
2006-07-16 19:39:23 +00:00
GtkTreePath *path;
GtkTreeIter iter;
TrTorrent *tor;
int status;
2006-07-16 19:39:23 +00:00
gboolean changed;
2006-08-20 22:45:29 +00:00
GtkWidget * win;
2006-07-16 19:39:23 +00:00
g_assert( ACTION_COUNT > act );
switch( act )
{
case ACT_ABOUT:
about ();
return;
case ACT_OPEN:
makeaddwind( data->wind, data->core );
return;
case ACT_PREF:
2007-02-19 22:09:05 +00:00
if( NULL != data->prefs )
{
2007-02-19 22:09:05 +00:00
return;
}
data->prefs = tr_prefs_new_with_parent( G_OBJECT( data->core ),
data->wind );
2007-02-19 22:09:05 +00:00
g_signal_connect( data->prefs, "destroy",
G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
gtk_widget_show( GTK_WIDGET( data->prefs ) );
return;
case ACT_DEBUG:
if( !data->msgwinopen )
{
data->msgwinopen = TRUE;
win = msgwin_create();
g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ),
&data->msgwinopen );
}
return;
2007-02-19 22:09:05 +00:00
case ACT_ICON:
iconclick( data );
2007-02-19 22:09:05 +00:00
return;
case ACT_CLOSE:
if( NULL != data->wind )
{
winclose( NULL, NULL, data );
}
return;
case ACT_QUIT:
askquit( data->wind, wannaquit, data );
return;
case ACT_SEPARATOR1:
case ACT_SEPARATOR2:
case ACT_SEPARATOR3:
return;
case ACT_START:
case ACT_STOP:
case ACT_DELETE:
case ACT_INFO:
case ACTION_COUNT:
break;
2006-07-16 19:39:23 +00:00
}
/* get a list of references to selected rows */
2007-02-19 22:09:05 +00:00
rows = getselection( data );
2006-07-16 19:39:23 +00:00
model = tr_core_model( data->core );
2006-07-16 19:39:23 +00:00
changed = FALSE;
for(ii = rows; NULL != ii; ii = ii->next) {
if(NULL != (path = gtk_tree_row_reference_get_path(ii->data)) &&
gtk_tree_model_get_iter( model, &iter, path ) )
{
gtk_tree_model_get( model , &iter, MC_TORRENT, &tor,
MC_STAT, &status, -1 );
if( ACT_ISAVAIL( actions[act].flags, status ) )
{
switch( act )
{
case ACT_START:
tr_torrent_start( tor );
changed = TRUE;
break;
case ACT_STOP:
tr_torrent_stop( tor );
changed = TRUE;
break;
case ACT_DELETE:
tr_core_delete_torrent( data->core, &iter );
changed = TRUE;
break;
case ACT_INFO:
gtk_widget_show (torrent_inspector_new (data->wind, tor));
break;
case ACT_OPEN:
case ACT_PREF:
case ACT_DEBUG:
2007-02-19 22:09:05 +00:00
case ACT_ICON:
case ACT_CLOSE:
case ACT_QUIT:
case ACT_SEPARATOR1:
case ACT_SEPARATOR2:
case ACT_SEPARATOR3:
case ACTION_COUNT:
break;
}
2006-07-16 19:39:23 +00:00
}
g_object_unref(tor);
}
if(NULL != path)
gtk_tree_path_free(path);
gtk_tree_row_reference_free(ii->data);
}
g_list_free(rows);
if(changed) {
updatemodel(data);
tr_core_save( data->core );
2006-07-16 19:39:23 +00:00
}
}
static void
2006-07-16 19:39:23 +00:00
safepipe(void) {
struct sigaction sa;
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
}
static void
2006-07-16 19:39:23 +00:00
setupsighandlers(void) {
2007-02-19 22:09:05 +00:00
int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM};
2006-07-16 19:39:23 +00:00
struct sigaction sa;
2007-02-19 22:09:05 +00:00
int ii;
2006-07-16 19:39:23 +00:00
bzero(&sa, sizeof(sa));
sa.sa_handler = fatalsig;
for(ii = 0; ii < ALEN(sigs); ii++)
sigaction(sigs[ii], &sa, NULL);
}
static void
2006-07-16 19:39:23 +00:00
fatalsig(int sig) {
struct sigaction sa;
if(SIGCOUNT_MAX <= ++global_sigcount) {
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_DFL;
sigaction(sig, &sa, NULL);
raise(sig);
}
}