mirror of
https://github.com/transmission/transmission.git
synced 2026-04-18 07:56:33 +01:00
Update 2005-11-18
This commit is contained in:
15
gtk/Jamfile
Normal file
15
gtk/Jamfile
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
SubDir TOP gtk ;
|
||||
|
||||
GTK_SRC = conf.c main.c util.c ;
|
||||
|
||||
local CCFLAGS = $(CCFLAGS) $(GTKCCFLAGS) ;
|
||||
#local LINKLIBS = $(LINKLIBS) $(GTKLINKLIBS) ;
|
||||
local HDRS = $(HDRS) $(TOP)/libtransmission ;
|
||||
|
||||
# jesus fucking christ, I give up
|
||||
LINKLIBS += $(GTKLINKLIBS) ;
|
||||
|
||||
Main transmission-gtk : $(GTK_SRC) ;
|
||||
LinkLibraries transmission-gtk : libtransmission.a ;
|
||||
}
|
||||
489
gtk/conf.c
Normal file
489
gtk/conf.c
Normal file
@@ -0,0 +1,489 @@
|
||||
/*
|
||||
Copyright (c) 2005 Joshua Elsasser. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "conf.h"
|
||||
#include "transmission.h"
|
||||
|
||||
#define FILE_LOCK "gtk_lock"
|
||||
#define FILE_PREFS "gtk_prefs"
|
||||
#define FILE_PREFS_TMP "gtk_prefs.tmp"
|
||||
#define FILE_STATE "gtk_state"
|
||||
#define FILE_STATE_TMP "gtk_state.tmp"
|
||||
#define PREF_SEP_KEYVAL '\t'
|
||||
#define PREF_SEP_LINE '\n'
|
||||
#define STATE_SEP '\n'
|
||||
|
||||
static int
|
||||
lockfile(const char *file, char **errstr);
|
||||
static gboolean
|
||||
writeprefs(char **errstr);
|
||||
static gboolean
|
||||
writefile_traverse(gpointer key, gpointer value, gpointer data);
|
||||
static char *
|
||||
getstateval(struct cf_torrentstate *state, char *line);
|
||||
|
||||
static char *confdir = NULL;
|
||||
static GTree *prefs = NULL;
|
||||
|
||||
static int
|
||||
lockfile(const char *file, char **errstr) {
|
||||
int fd, savederr;
|
||||
struct flock lk;
|
||||
|
||||
*errstr = NULL;
|
||||
|
||||
if(0 > (fd = open(file, O_RDWR | O_CREAT, 0666))) {
|
||||
savederr = errno;
|
||||
*errstr = g_strdup_printf("Error opening file %s for writing:\n%s",
|
||||
file, strerror(errno));
|
||||
errno = savederr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bzero(&lk, sizeof(lk));
|
||||
lk.l_start = 0;
|
||||
lk.l_len = 0;
|
||||
lk.l_type = F_WRLCK;
|
||||
lk.l_whence = SEEK_SET;
|
||||
if(-1 == fcntl(fd, F_SETLK, &lk)) {
|
||||
savederr = errno;
|
||||
if(EAGAIN == errno)
|
||||
*errstr = g_strdup_printf("Another copy of %s is already running.",
|
||||
g_get_application_name());
|
||||
else
|
||||
*errstr = g_strdup_printf("Error obtaining lock on file %s:\n%s",
|
||||
file, strerror(errno));
|
||||
close(fd);
|
||||
errno = savederr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
gboolean
|
||||
cf_init(const char *dir, char **errstr) {
|
||||
struct stat sb;
|
||||
|
||||
*errstr = NULL;
|
||||
confdir = g_strdup(dir);
|
||||
|
||||
if(0 > stat(dir, &sb)) {
|
||||
if(ENOENT != errno)
|
||||
*errstr = g_strdup_printf("Failed to check directory %s:\n%s",
|
||||
dir, strerror(errno));
|
||||
else {
|
||||
if(0 == mkdir(dir, 0777))
|
||||
return TRUE;
|
||||
else
|
||||
*errstr = g_strdup_printf("Failed to create directory %s:\n%s",
|
||||
dir, strerror(errno));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(S_IFDIR & sb.st_mode)
|
||||
return TRUE;
|
||||
|
||||
*errstr = g_strdup_printf("%s is not a directory", dir);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
cf_lock(char **errstr) {
|
||||
char *path = g_build_filename(confdir, FILE_LOCK, NULL);
|
||||
int fd = lockfile(path, errstr);
|
||||
|
||||
g_free(path);
|
||||
return 0 <= fd;
|
||||
}
|
||||
|
||||
gboolean
|
||||
cf_loadprefs(char **errstr) {
|
||||
char *path = g_build_filename(confdir, FILE_PREFS, NULL);
|
||||
GIOChannel *io;
|
||||
GError *err;
|
||||
char *line, *sep;
|
||||
gsize len, termpos;
|
||||
char term = PREF_SEP_LINE;
|
||||
|
||||
*errstr = NULL;
|
||||
|
||||
if(NULL != prefs)
|
||||
g_tree_destroy(prefs);
|
||||
|
||||
prefs = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL,
|
||||
g_free, g_free);
|
||||
|
||||
err = NULL;
|
||||
io = g_io_channel_new_file(path, "r", &err);
|
||||
if(NULL != err) {
|
||||
if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
|
||||
*errstr = g_strdup_printf("Error opening file %s for reading:\n%s",
|
||||
path, err->message);
|
||||
goto done;
|
||||
}
|
||||
/*g_io_channel_set_encoding(io, NULL, NULL);*/
|
||||
g_io_channel_set_line_term(io, &term, 1);
|
||||
|
||||
err = NULL;
|
||||
for(;;) {
|
||||
assert(NULL == err) ;
|
||||
switch(g_io_channel_read_line(io, &line, &len, &termpos, &err)) {
|
||||
case G_IO_STATUS_ERROR:
|
||||
*errstr = g_strdup_printf("Error reading file %s:\n%s",
|
||||
path, err->message);
|
||||
goto done;
|
||||
case G_IO_STATUS_NORMAL:
|
||||
if(NULL != line) {
|
||||
if(NULL != (sep = strchr(line, PREF_SEP_KEYVAL)) && sep > line) {
|
||||
*sep = '\0';
|
||||
line[termpos] = '\0';
|
||||
g_tree_insert(prefs, g_strcompress(line), g_strcompress(sep + 1));
|
||||
}
|
||||
g_free(line);
|
||||
}
|
||||
break;
|
||||
case G_IO_STATUS_EOF:
|
||||
goto done;
|
||||
default:
|
||||
assert(!"unknown return code");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if(NULL != err)
|
||||
g_error_free(err);
|
||||
if(NULL != io)
|
||||
g_io_channel_unref(io);
|
||||
return NULL == *errstr;
|
||||
}
|
||||
|
||||
const char *
|
||||
cf_getpref(const char *name) {
|
||||
assert(NULL != prefs);
|
||||
|
||||
return g_tree_lookup(prefs, name);
|
||||
}
|
||||
|
||||
gboolean
|
||||
cf_setpref(const char *name, const char *value, char **errstr) {
|
||||
assert(NULL != prefs);
|
||||
|
||||
g_tree_insert(prefs, g_strdup(name), g_strdup(value));
|
||||
|
||||
return writeprefs(errstr);
|
||||
}
|
||||
|
||||
struct writeinfo {
|
||||
GIOChannel *io;
|
||||
GError *err;
|
||||
};
|
||||
|
||||
static gboolean
|
||||
writeprefs(char **errstr) {
|
||||
char *file = g_build_filename(confdir, FILE_PREFS, NULL);
|
||||
char *tmpfile = g_build_filename(confdir, FILE_PREFS_TMP, NULL);
|
||||
GIOChannel *io = NULL;
|
||||
struct writeinfo info;
|
||||
int fd;
|
||||
|
||||
assert(NULL != prefs);
|
||||
|
||||
*errstr = NULL;
|
||||
|
||||
if(0 > (fd = lockfile(tmpfile, errstr))) {
|
||||
g_free(errstr);
|
||||
*errstr = g_strdup_printf("Error opening or locking file %s:\n%s",
|
||||
tmpfile, strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
info.err = NULL;
|
||||
io = g_io_channel_unix_new(fd);
|
||||
/*g_io_channel_set_encoding(io, NULL, NULL);*/
|
||||
g_io_channel_set_close_on_unref(io, TRUE);
|
||||
|
||||
info.io = io;
|
||||
info.err = NULL;
|
||||
g_tree_foreach(prefs, writefile_traverse, &info);
|
||||
if(NULL != info.err ||
|
||||
G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &info.err)) {
|
||||
*errstr = g_strdup_printf("Error writing to file %s:\n%s",
|
||||
tmpfile, info.err->message);
|
||||
g_error_free(info.err);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if(0 > rename(tmpfile, file)) {
|
||||
*errstr = g_strdup_printf("Error renaming %s to %s:\n%s",
|
||||
tmpfile, file, strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
g_free(file);
|
||||
g_free(tmpfile);
|
||||
if(NULL != io)
|
||||
g_io_channel_unref(io);
|
||||
|
||||
return NULL == *errstr;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
writefile_traverse(gpointer key, gpointer value, gpointer data) {
|
||||
struct writeinfo *info = data;
|
||||
char *ekey, *eval, *line;
|
||||
char sep[2];
|
||||
int len;
|
||||
|
||||
ekey = g_strescape(key, NULL);
|
||||
eval = g_strescape(value, NULL);
|
||||
sep[0] = PREF_SEP_KEYVAL;
|
||||
sep[1] = '\0';
|
||||
line = g_strjoin(sep, ekey, eval, NULL);
|
||||
len = strlen(line);
|
||||
line[len] = PREF_SEP_LINE;
|
||||
|
||||
switch(g_io_channel_write_chars(info->io, line, len + 1, NULL, &info->err)) {
|
||||
case G_IO_STATUS_ERROR:
|
||||
goto done;
|
||||
case G_IO_STATUS_NORMAL:
|
||||
break;
|
||||
default:
|
||||
assert(!"unknown return code");
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
g_free(ekey);
|
||||
g_free(eval);
|
||||
g_free(line);
|
||||
return NULL != info->err;
|
||||
}
|
||||
|
||||
GList *
|
||||
cf_loadstate(char **errstr) {
|
||||
char *path = g_build_filename(confdir, FILE_STATE, NULL);
|
||||
GIOChannel *io;
|
||||
GError *err;
|
||||
char term = STATE_SEP;
|
||||
GList *ret = NULL;
|
||||
char *line, *ptr;
|
||||
gsize len, termpos;
|
||||
struct cf_torrentstate *ts;
|
||||
|
||||
err = NULL;
|
||||
io = g_io_channel_new_file(path, "r", &err);
|
||||
if(NULL != err) {
|
||||
if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
|
||||
*errstr = g_strdup_printf("Error opening file %s for reading:\n%s",
|
||||
path, err->message);
|
||||
goto done;
|
||||
}
|
||||
/*g_io_channel_set_encoding(io, NULL, NULL);*/
|
||||
g_io_channel_set_line_term(io, &term, 1);
|
||||
|
||||
err = NULL;
|
||||
for(;;) {
|
||||
assert(NULL == err);
|
||||
switch(g_io_channel_read_line(io, &line, &len, &termpos, &err)) {
|
||||
case G_IO_STATUS_ERROR:
|
||||
*errstr = g_strdup_printf("Error reading file %s:\n%s",
|
||||
path, err->message);
|
||||
goto done;
|
||||
case G_IO_STATUS_NORMAL:
|
||||
if(NULL != line) {
|
||||
ts = g_new0(struct cf_torrentstate, 1);
|
||||
ptr = line;
|
||||
while(NULL != (ptr = getstateval(ts, ptr)))
|
||||
;
|
||||
g_free(line);
|
||||
if(NULL != ts->ts_torrent && NULL != ts->ts_directory)
|
||||
ret = g_list_append(ret, ts);
|
||||
else
|
||||
cf_freestate(ts);
|
||||
}
|
||||
break;
|
||||
case G_IO_STATUS_EOF:
|
||||
goto done;
|
||||
default:
|
||||
assert(!"unknown return code");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if(NULL != err)
|
||||
g_error_free(err);
|
||||
if(NULL != io)
|
||||
g_io_channel_unref(io);
|
||||
if(NULL != *errstr && NULL != ret) {
|
||||
g_list_foreach(ret, (GFunc)g_free, NULL);
|
||||
g_list_free(ret);
|
||||
ret = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *
|
||||
getstateval(struct cf_torrentstate *state, char *line) {
|
||||
char *start, *end;
|
||||
|
||||
while(isspace(*line))
|
||||
line++;
|
||||
|
||||
if(NULL == (start = strchr(line, '=')))
|
||||
return NULL;
|
||||
|
||||
while(isspace(*(++start)))
|
||||
;
|
||||
|
||||
if('"' != *start)
|
||||
return NULL;
|
||||
|
||||
for(end = ++start; '\0' != *end && '"' != *end; end++)
|
||||
if('\\' == *end && '\0' != *(end + 1))
|
||||
end++;
|
||||
|
||||
if('"' != *end)
|
||||
return NULL;
|
||||
|
||||
if(0 == memcmp(line, "torrent", sizeof("torrent") - 1)) {
|
||||
state->ts_torrent = g_new(char, end - start + 1);
|
||||
memcpy(state->ts_torrent, start, end - start);
|
||||
state->ts_torrent[end - start] = '\0';
|
||||
}
|
||||
else if(0 == memcmp(line, "dir", sizeof("dir") - 1)) {
|
||||
state->ts_directory = g_new(char, end - start + 1);
|
||||
memcpy(state->ts_directory, start, end - start);
|
||||
state->ts_directory[end - start] = '\0';
|
||||
}
|
||||
else if(0 == memcmp(line, "paused", sizeof("paused") - 1)) {
|
||||
state->ts_paused = (0 == memcmp("yes", start, end - start));
|
||||
}
|
||||
|
||||
return end + 1;
|
||||
}
|
||||
|
||||
/* XXX need to save download directory, also maybe running/stopped state */
|
||||
gboolean
|
||||
cf_savestate(int count, tr_stat_t *torrents, char **errstr) {
|
||||
char *file = g_build_filename(confdir, FILE_STATE, NULL);
|
||||
char *tmpfile = g_build_filename(confdir, FILE_STATE_TMP, NULL);
|
||||
GIOChannel *io = NULL;
|
||||
GError *err;
|
||||
int fd, ii;
|
||||
char *torrentfile, *torrentdir, *line;
|
||||
gsize written;
|
||||
gboolean paused;
|
||||
GIOStatus res;
|
||||
|
||||
*errstr = NULL;
|
||||
|
||||
if(0 > (fd = lockfile(tmpfile, errstr))) {
|
||||
g_free(errstr);
|
||||
*errstr = g_strdup_printf("Error opening or locking file %s:\n%s",
|
||||
tmpfile, strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
io = g_io_channel_unix_new(fd);
|
||||
/* XXX what the hell should I be doing about unicode? */
|
||||
/*g_io_channel_set_encoding(io, NULL, NULL);*/
|
||||
g_io_channel_set_close_on_unref(io, TRUE);
|
||||
|
||||
err = NULL;
|
||||
for(ii = 0; ii < count; ii++) {
|
||||
/* XXX need a better way to query running/stopped state */
|
||||
paused = (TR_STATUS_STOPPING == torrents[ii].status ||
|
||||
TR_STATUS_STOPPED == torrents[ii].status ||
|
||||
TR_STATUS_PAUSE == torrents[ii].status);
|
||||
torrentfile = g_strescape(torrents[ii].info.torrent, "");
|
||||
torrentdir = g_strescape(torrents[ii].folder, "");
|
||||
/* g_strcompress */
|
||||
line = g_strdup_printf("torrent=\"%s\" dir=\"%s\" paused=\"%s\"%c",
|
||||
torrentfile, torrentdir, (paused ? "yes" : "no"),
|
||||
STATE_SEP);
|
||||
res = g_io_channel_write_chars(io, line, strlen(line), &written, &err);
|
||||
g_free(torrentfile);
|
||||
g_free(torrentdir);
|
||||
g_free(line);
|
||||
switch(res) {
|
||||
case G_IO_STATUS_ERROR:
|
||||
goto done;
|
||||
case G_IO_STATUS_NORMAL:
|
||||
break;
|
||||
default:
|
||||
assert(!"unknown return code");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if(NULL != err ||
|
||||
G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &err)) {
|
||||
*errstr = g_strdup_printf("Error writing to file %s:\n%s",
|
||||
tmpfile, err->message);
|
||||
g_error_free(err);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if(0 > rename(tmpfile, file)) {
|
||||
*errstr = g_strdup_printf("Error renaming %s to %s:\n%s",
|
||||
tmpfile, file, strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
g_free(file);
|
||||
g_free(tmpfile);
|
||||
if(NULL != io)
|
||||
g_io_channel_unref(io);
|
||||
|
||||
return NULL == *errstr;
|
||||
}
|
||||
|
||||
void
|
||||
cf_freestate(struct cf_torrentstate *state) {
|
||||
if(NULL != state->ts_torrent)
|
||||
g_free(state->ts_torrent);
|
||||
if(NULL != state->ts_directory)
|
||||
g_free(state->ts_directory);
|
||||
g_free(state);
|
||||
}
|
||||
55
gtk/conf.h
Normal file
55
gtk/conf.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright (c) 2005 Joshua Elsasser. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef TG_CONF_H
|
||||
#define TG_CONF_H
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
struct cf_torrentstate {
|
||||
char *ts_torrent;
|
||||
char *ts_directory;
|
||||
gboolean ts_paused;
|
||||
};
|
||||
|
||||
gboolean
|
||||
cf_init(const char *confdir, char **errstr);
|
||||
gboolean
|
||||
cf_lock(char **errstr);
|
||||
gboolean
|
||||
cf_loadprefs(char **errstr);
|
||||
const char *
|
||||
cf_getpref(const char *name);
|
||||
gboolean
|
||||
cf_setpref(const char *name, const char *value, char **errstr);
|
||||
GList *
|
||||
cf_loadstate(char **errstr);
|
||||
gboolean
|
||||
cf_savestate(int count, tr_stat_t *torrents, char **errstr);
|
||||
void
|
||||
cf_freestate(struct cf_torrentstate *state);
|
||||
|
||||
#endif /* TG_CONF_H */
|
||||
879
gtk/main.c
Normal file
879
gtk/main.c
Normal file
@@ -0,0 +1,879 @@
|
||||
/*
|
||||
Copyright (c) 2005 Joshua Elsasser. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/param.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "conf.h"
|
||||
#include "transmission.h"
|
||||
#include "util.h"
|
||||
|
||||
#define TRACKER_EXIT_TIMEOUT 30
|
||||
|
||||
struct cbdata {
|
||||
tr_handle_t *tr;
|
||||
GtkWidget *wind;
|
||||
GtkListStore *model;
|
||||
GtkTreeView *view;
|
||||
guint timer;
|
||||
};
|
||||
|
||||
struct exitdata {
|
||||
struct cbdata *cbdata;
|
||||
time_t started;
|
||||
guint timer;
|
||||
};
|
||||
|
||||
struct pieces {
|
||||
char p[120];
|
||||
};
|
||||
|
||||
void
|
||||
readargs(int argc, char **argv, int *port, int *limit);
|
||||
void
|
||||
maketypes(void);
|
||||
gpointer
|
||||
tr_pieces_copy(gpointer);
|
||||
void
|
||||
tr_pieces_free(gpointer);
|
||||
|
||||
void
|
||||
makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved);
|
||||
gboolean
|
||||
winclose(GtkWidget *widget, GdkEvent *event, gpointer gdata);
|
||||
gboolean
|
||||
exitcheck(gpointer gdata);
|
||||
GtkWidget *
|
||||
makewind_toolbar(struct cbdata *data);
|
||||
GtkWidget *
|
||||
makewind_list(struct cbdata *data);
|
||||
|
||||
gboolean
|
||||
updatemodel(gpointer gdata);
|
||||
gboolean
|
||||
listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata);
|
||||
gboolean
|
||||
listpopup(GtkWidget *widget, gpointer userdata);
|
||||
void
|
||||
dopopupmenu(GtkWidget *widget, GdkEventButton *event, struct cbdata *data);
|
||||
void
|
||||
actionclick(GtkWidget *widget, gpointer gdata);
|
||||
|
||||
void
|
||||
makeaddwind(struct cbdata *data);
|
||||
gboolean
|
||||
addtorrent(tr_handle_t *tr, GtkWidget *parentwind, const char *torrent,
|
||||
const char *dir, gboolean paused);
|
||||
void
|
||||
fileclick(GtkWidget *widget, gpointer gdata);
|
||||
const char *
|
||||
statusstr(int status);
|
||||
void
|
||||
makeinfowind(struct cbdata *data, int index);
|
||||
gboolean
|
||||
savetorrents(tr_handle_t *tr, GtkWidget *wind, int count, tr_stat_t *stat);
|
||||
|
||||
#define TR_TYPE_PIECES_NAME "tr-type-pieces"
|
||||
#define TR_TYPE_PIECES ((const GType)tr_type_pieces)
|
||||
#define TR_PIECES(ptr) ((struct pieces*)ptr)
|
||||
GType tr_type_pieces;
|
||||
|
||||
#define LIST_ACTION "torrent-list-action"
|
||||
enum listact { ACT_OPEN, ACT_START, ACT_STOP, ACT_DELETE, ACT_INFO };
|
||||
#define LIST_ACTION_FROM "torrent-list-action-from"
|
||||
enum listfrom { FROM_BUTTON, FROM_POPUP };
|
||||
|
||||
#define LIST_INDEX "torrent-list-index"
|
||||
|
||||
struct { gint pos; const gchar *name; const gchar *id; enum listact act;
|
||||
const char *ttext; const char *tpriv; }
|
||||
actionitems[] = {
|
||||
{0, "Add", GTK_STOCK_ADD, ACT_OPEN,
|
||||
"Add a new torrent file", "XXX"},
|
||||
{1, "Resume", GTK_STOCK_MEDIA_PLAY, ACT_START,
|
||||
"Resume a torrent that has been paused", "XXX"},
|
||||
{2, "Pause", GTK_STOCK_MEDIA_PAUSE, ACT_STOP,
|
||||
"Pause a torrent", "XXX"},
|
||||
{3, "Remove", GTK_STOCK_REMOVE, ACT_DELETE,
|
||||
"Remove a torrent from the list", "XXX"},
|
||||
{4, "Properties", GTK_STOCK_PROPERTIES, ACT_INFO,
|
||||
"Get additional information for a torrent", "XXX"},
|
||||
};
|
||||
|
||||
#define CBDATA_PTR "callback-data-pointer"
|
||||
int
|
||||
main(int argc, char **argv) {
|
||||
GtkWidget *mainwind, *preferr, *stateerr;
|
||||
char *err;
|
||||
int port, limit;
|
||||
tr_handle_t *tr;
|
||||
GList *saved;
|
||||
|
||||
gtk_init(&argc, &argv);
|
||||
readargs(argc, argv, &port, &limit);
|
||||
|
||||
tr = tr_init();
|
||||
|
||||
if(cf_init(tr_getPrefsDirectory(tr), &err)) {
|
||||
if(cf_lock(&err)) {
|
||||
/* create main window now so any error dialogs can be it's children */
|
||||
mainwind = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
preferr = NULL;
|
||||
stateerr = NULL;
|
||||
|
||||
if(!cf_loadprefs(&err)) {
|
||||
preferr = errmsg(mainwind, "%s", err);
|
||||
g_free(err);
|
||||
}
|
||||
saved = cf_loadstate(&err);
|
||||
if(NULL != err) {
|
||||
stateerr = errmsg(mainwind, "%s", err);
|
||||
g_free(err);
|
||||
}
|
||||
|
||||
/* XXX need to remove port and limit options and make them prefs */
|
||||
/* XXX need prefs gui */
|
||||
/* XXX need default save dir pref */
|
||||
|
||||
if(0 != port)
|
||||
tr_setBindPort(tr, port);
|
||||
if(0 != limit)
|
||||
tr_setUploadLimit(tr, limit);
|
||||
|
||||
maketypes();
|
||||
makewind(mainwind, tr, saved);
|
||||
|
||||
if(NULL != preferr)
|
||||
gtk_widget_show_all(preferr);
|
||||
if(NULL != stateerr)
|
||||
gtk_widget_show_all(stateerr);
|
||||
} else {
|
||||
errmsg_full(NULL, (errfunc_t)gtk_main_quit, NULL, "%s", err);
|
||||
g_free(err);
|
||||
}
|
||||
} else {
|
||||
errmsg_full(NULL, (errfunc_t)gtk_main_quit, NULL, "%s", err);
|
||||
g_free(err);
|
||||
}
|
||||
|
||||
gtk_main();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
readargs(int argc, char **argv, int *port, int *limit) {
|
||||
char *name;
|
||||
int opt, num;
|
||||
|
||||
*port = 0;
|
||||
*limit = 0;
|
||||
|
||||
if(NULL == (name = strrchr(argv[0], '/')) || '\0' == *(++name))
|
||||
name = argv[0];
|
||||
|
||||
for(num = 1; num < argc; num++)
|
||||
if(0 == strcmp(argv[num], "-help") || 0 == strcmp(argv[num], "--help"))
|
||||
goto usage;
|
||||
|
||||
while(0 <= (opt = getopt(argc, argv, "hp:u:"))) {
|
||||
switch(opt) {
|
||||
case 'p':
|
||||
num = atoi(optarg);
|
||||
if(0 < num && 0xffff > num)
|
||||
*port = num;
|
||||
break;
|
||||
case 'u':
|
||||
num = atoi(optarg);
|
||||
if(0 != num)
|
||||
*limit = num;
|
||||
break;
|
||||
default:
|
||||
goto usage;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
usage:
|
||||
printf("usage: %s [-h] [-p port] [-u limit]\n", name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void
|
||||
maketypes(void) {
|
||||
tr_type_pieces = g_boxed_type_register_static(
|
||||
TR_TYPE_PIECES_NAME, tr_pieces_copy, tr_pieces_free);
|
||||
}
|
||||
|
||||
gpointer
|
||||
tr_pieces_copy(gpointer data) {
|
||||
return g_memdup(data, sizeof(struct pieces));
|
||||
}
|
||||
|
||||
void
|
||||
tr_pieces_free(gpointer data) {
|
||||
g_free(data);
|
||||
}
|
||||
|
||||
void
|
||||
makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
|
||||
GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
|
||||
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
|
||||
struct cbdata *data = g_new0(struct cbdata, 1);
|
||||
GtkWidget *list;
|
||||
GtkRequisition req;
|
||||
GList *ii;
|
||||
struct cf_torrentstate *ts;
|
||||
|
||||
data->tr = tr;
|
||||
data->wind = wind;
|
||||
data->timer = -1;
|
||||
/* filled in by makewind_list */
|
||||
data->model = NULL;
|
||||
data->view = NULL;
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(vbox), makewind_toolbar(data), FALSE, FALSE, 0);
|
||||
|
||||
list = makewind_list(data);
|
||||
gtk_widget_size_request(list, &req);
|
||||
gtk_container_add(GTK_CONTAINER(scroll), list);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 5);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(wind), vbox);
|
||||
gtk_window_set_default_size(GTK_WINDOW(wind), req.width, req.height);
|
||||
g_signal_connect(G_OBJECT(wind), "delete_event", G_CALLBACK(winclose), data);
|
||||
gtk_widget_show_all(wind);
|
||||
|
||||
for(ii = g_list_first(saved); NULL != ii; ii = ii->next) {
|
||||
ts = ii->data;
|
||||
addtorrent(tr, wind, ts->ts_torrent, ts->ts_directory, ts->ts_paused);
|
||||
cf_freestate(ts);
|
||||
}
|
||||
g_list_free(saved);
|
||||
|
||||
data->timer = g_timeout_add(500, updatemodel, data);
|
||||
updatemodel(data);
|
||||
}
|
||||
|
||||
/* XXX is this the right thing to do? */
|
||||
#define TR_TORRENT_NEEDS_STOP(t) \
|
||||
((t) & TR_STATUS_CHECK || (t) & TR_STATUS_DOWNLOAD || (t) & TR_STATUS_SEED)
|
||||
|
||||
gboolean
|
||||
winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
|
||||
struct cbdata *data = gdata;
|
||||
struct exitdata *edata;
|
||||
tr_stat_t *st;
|
||||
int ii;
|
||||
|
||||
if(0 >= data->timer)
|
||||
g_source_remove(data->timer);
|
||||
data->timer = -1;
|
||||
|
||||
for(ii = tr_torrentStat(data->tr, &st); 0 < ii; ii--) {
|
||||
if(TR_TORRENT_NEEDS_STOP(st[ii-1].status)) {
|
||||
fprintf(stderr, "quit: stopping %i %s\n", ii, st[ii-1].info.name);
|
||||
tr_torrentStop(data->tr, ii - 1);
|
||||
} else {
|
||||
fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);
|
||||
tr_torrentClose(data->tr, ii - 1);
|
||||
}
|
||||
}
|
||||
free(st);
|
||||
|
||||
/* XXX should disable widgets or something */
|
||||
|
||||
/* try to wait until torrents stop before exiting */
|
||||
edata = g_new0(struct exitdata, 1);
|
||||
edata->cbdata = data;
|
||||
edata->started = time(NULL);
|
||||
edata->timer = g_timeout_add(500, exitcheck, edata);
|
||||
|
||||
fprintf(stderr, "quit: starting timeout at %i\n", edata->started);
|
||||
|
||||
//exitcheck(edata);
|
||||
|
||||
/* returning FALSE means to destroy the window */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
exitcheck(gpointer gdata) {
|
||||
struct exitdata *data = gdata;
|
||||
tr_stat_t *st;
|
||||
int ii;
|
||||
|
||||
for(ii = tr_torrentStat(data->cbdata->tr, &st); 0 < ii; ii--) {
|
||||
if(TR_STATUS_PAUSE == st[ii-1].status) {
|
||||
fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);
|
||||
tr_torrentClose(data->cbdata->tr, ii - 1);
|
||||
}
|
||||
}
|
||||
free(st);
|
||||
|
||||
fprintf(stderr, "quit: %i torrents left at %i\n",
|
||||
tr_torrentCount(data->cbdata->tr), time(NULL));
|
||||
/* keep going if we still have torrents and haven't hit the exit timeout */
|
||||
if(0 < tr_torrentCount(data->cbdata->tr) &&
|
||||
time(NULL) - data->started < TRACKER_EXIT_TIMEOUT) {
|
||||
updatemodel(data->cbdata);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
fprintf(stderr, "quit: giving up on %i torrents\n",
|
||||
tr_torrentCount(data->cbdata->tr));
|
||||
|
||||
for(ii = tr_torrentCount(data->cbdata->tr); 0 < ii; ii--)
|
||||
tr_torrentClose(data->cbdata->tr, ii - 1);
|
||||
|
||||
/* exit otherwise */
|
||||
|
||||
if(0 >= data->timer)
|
||||
g_source_remove(data->timer);
|
||||
data->timer = -1;
|
||||
|
||||
gtk_widget_destroy(data->cbdata->wind);
|
||||
tr_close(data->cbdata->tr);
|
||||
g_free(data->cbdata);
|
||||
g_free(data);
|
||||
gtk_main_quit();
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
makewind_toolbar(struct cbdata *data) {
|
||||
GtkWidget *bar = gtk_toolbar_new();
|
||||
GtkToolItem *item;
|
||||
unsigned int ii;
|
||||
|
||||
gtk_toolbar_set_tooltips(GTK_TOOLBAR(bar), TRUE);
|
||||
gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_BOTH);
|
||||
|
||||
for(ii = 0; ii < sizeof(actionitems) / sizeof(actionitems[0]); ii++) {
|
||||
item = gtk_tool_button_new_from_stock(actionitems[ii].id);
|
||||
gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), actionitems[ii].name);
|
||||
gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(item), GTK_TOOLBAR(bar)->tooltips,
|
||||
actionitems[ii].ttext, actionitems[ii].tpriv);
|
||||
g_object_set_data(G_OBJECT(item), LIST_ACTION,
|
||||
GINT_TO_POINTER(actionitems[ii].act));
|
||||
g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
|
||||
GINT_TO_POINTER(FROM_BUTTON));
|
||||
g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(actionclick), data);
|
||||
gtk_toolbar_insert(GTK_TOOLBAR(bar), GTK_TOOL_ITEM(item), actionitems[ii].pos);
|
||||
}
|
||||
|
||||
return bar;
|
||||
}
|
||||
|
||||
enum {MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_PROG, MC_DRATE, MC_URATE,
|
||||
MC_ETA, MC_PEERS, MC_UPEERS, MC_DPEERS, MC_PIECES, MC_DOWN, MC_UP,
|
||||
MC_ROW_INDEX, MC_ROW_COUNT};
|
||||
|
||||
GtkWidget *
|
||||
makewind_list(struct cbdata *data) {
|
||||
GType types[] = {
|
||||
/* info->name, info->totalSize, status, error, progress */
|
||||
G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INT, G_TYPE_STRING, G_TYPE_INT,
|
||||
/* rateDownload, rateUpload, eta, peersTotal, peersUploading */
|
||||
G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
|
||||
/* peersDownloading, pieces, downloaded, uploaded */
|
||||
G_TYPE_INT, TR_TYPE_PIECES, G_TYPE_UINT64, G_TYPE_UINT64,
|
||||
/* index into the torrent array */
|
||||
G_TYPE_INT};
|
||||
GtkListStore *model;
|
||||
GtkWidget *view;
|
||||
/*GtkTreeViewColumn *col;*/
|
||||
GtkTreeSelection *sel;
|
||||
GtkCellRenderer *rend;
|
||||
GtkCellRenderer *rendprog;
|
||||
|
||||
assert(MC_ROW_COUNT == sizeof(types) / sizeof(types[0]));
|
||||
|
||||
model = gtk_list_store_newv(MC_ROW_COUNT, types);
|
||||
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
|
||||
/* XXX do I need to worry about reference counts anywhere else? */
|
||||
g_object_unref(G_OBJECT(model));
|
||||
data->model = model;
|
||||
data->view = GTK_TREE_VIEW(view);
|
||||
|
||||
rend = gtk_cell_renderer_text_new();
|
||||
rendprog = gtk_cell_renderer_progress_new();
|
||||
g_object_set(rendprog, "text", "", NULL);
|
||||
|
||||
/*
|
||||
col = gtk_tree_view_column_new_with_attributes(
|
||||
"Name", rend, "text", MC_NAME, NULL);
|
||||
gtk_tree_view_column_add_attribute(col, rend, "text", MC_SIZE);
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
||||
|
||||
col = gtk_tree_view_column_new_with_attributes(
|
||||
"Status", rend, "text", MC_STAT, NULL);
|
||||
gtk_tree_view_column_add_attribute(col, rend, "text", MC_ERR);
|
||||
gtk_tree_view_column_add_attribute(col, rend, "text", MC_DPEERS);
|
||||
gtk_tree_view_column_add_attribute(col, rend, "text", MC_UPEERS);
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
||||
|
||||
col = gtk_tree_view_column_new_with_attributes(
|
||||
"Progress", rendprog, "value", MC_PROG, NULL);
|
||||
gtk_tree_view_column_pack_start(col, rend, TRUE);
|
||||
gtk_tree_view_column_add_attribute(col, rend, "text", MC_ETA);
|
||||
gtk_tree_view_column_add_attribute(col, rend, "text", MC_DRATE);
|
||||
gtk_tree_view_column_add_attribute(col, rend, "text", MC_URATE);
|
||||
gtk_tree_view_column_add_attribute(col, rend, "text", MC_DOWN);
|
||||
gtk_tree_view_column_add_attribute(col, rend, "text", MC_UP);
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
|
||||
*/
|
||||
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Name", rend,
|
||||
"text", MC_NAME, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Size", rend,
|
||||
"text", MC_SIZE, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Status", rend,
|
||||
"text", MC_STAT, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Error", rend,
|
||||
"text", MC_ERR, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Progress", rendprog,
|
||||
"value", MC_PROG, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Download Rate", rend,
|
||||
"text", MC_DRATE, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Upload Rate", rend,
|
||||
"text", MC_URATE, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("ETA", rend,
|
||||
"text", MC_ETA, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Peers", rend,
|
||||
"text", MC_PEERS, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Seeders", rend,
|
||||
"text", MC_UPEERS, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Leechers", rend,
|
||||
"text", MC_DPEERS, NULL));
|
||||
/*gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("", rend,
|
||||
"text", MC_PIECES, NULL));*/
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Downloaded", rend,
|
||||
"text", MC_DOWN, NULL));
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
|
||||
gtk_tree_view_column_new_with_attributes("Uploaded", rend,
|
||||
"text", MC_UP, NULL));
|
||||
|
||||
gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
|
||||
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
|
||||
gtk_tree_selection_set_mode(GTK_TREE_SELECTION(sel), GTK_SELECTION_SINGLE);
|
||||
g_signal_connect(G_OBJECT(view), "button-press-event",
|
||||
G_CALLBACK(listclick), data);
|
||||
g_signal_connect(G_OBJECT(view), "popup-menu", G_CALLBACK(listpopup), data);
|
||||
gtk_widget_show_all(view);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
gboolean
|
||||
updatemodel(gpointer gdata) {
|
||||
struct cbdata *data = gdata;
|
||||
tr_stat_t *st;
|
||||
int ii, max, prog;
|
||||
GtkTreeIter iter;
|
||||
|
||||
max = tr_torrentStat(data->tr, &st);
|
||||
for(ii = 0; ii < max; ii++) {
|
||||
if(!(ii ? gtk_tree_model_iter_next(GTK_TREE_MODEL(data->model), &iter) :
|
||||
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(data->model), &iter)))
|
||||
gtk_list_store_append(data->model, &iter);
|
||||
if(0.0 > (prog = st[ii].progress * 100.0))
|
||||
prog = 0;
|
||||
else if(100 < prog)
|
||||
prog = 100;
|
||||
/* XXX find out if setting the same data emits changed signal */
|
||||
gtk_list_store_set(data->model, &iter, MC_ROW_INDEX, ii,
|
||||
MC_NAME, st[ii].info.name, MC_SIZE, st[ii].info.totalSize, MC_STAT, st[ii].status,
|
||||
MC_ERR, st[ii].error, MC_PROG, prog, MC_DRATE, st[ii].rateDownload,
|
||||
MC_URATE, st[ii].rateUpload, MC_ETA, st[ii].eta, MC_PEERS, st[ii].peersTotal,
|
||||
MC_UPEERS, st[ii].peersUploading, MC_DPEERS, st[ii].peersDownloading,
|
||||
/*MC_PIECES, st[ii].pieces,*/ MC_DOWN, st[ii].downloaded, MC_UP, st[ii].uploaded, -1);
|
||||
}
|
||||
free(st);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata) {
|
||||
struct cbdata *data = gdata;
|
||||
|
||||
if(GDK_BUTTON_PRESS == event->type && 3 == event->button) {
|
||||
dopopupmenu(widget, event, data);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
listpopup(GtkWidget *widget, gpointer userdata) {
|
||||
dopopupmenu(widget, NULL, userdata);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
dopopupmenu(GtkWidget *widget SHUTUP, GdkEventButton *event,
|
||||
struct cbdata *data) {
|
||||
GtkWidget *menu = gtk_menu_new();
|
||||
GtkWidget *item;
|
||||
GtkTreePath *path;
|
||||
GtkTreeIter iter;
|
||||
unsigned int ii;
|
||||
int index;
|
||||
|
||||
index = -1;
|
||||
if(NULL != event && gtk_tree_view_get_path_at_pos(
|
||||
data->view, event->x, event->y, &path, NULL, NULL, NULL)) {
|
||||
if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model), &iter, path))
|
||||
gtk_tree_model_get(GTK_TREE_MODEL(data->model), &iter, MC_ROW_INDEX, &index, -1);
|
||||
gtk_tree_path_free(path);
|
||||
}
|
||||
|
||||
/* XXX am I leaking references here? */
|
||||
/* XXX can I cache this in cbdata? */
|
||||
for(ii = 0; ii < sizeof(actionitems) / sizeof(actionitems[0]); ii++) {
|
||||
item = gtk_menu_item_new_with_label(actionitems[ii].name);
|
||||
g_object_set_data(G_OBJECT(item), LIST_ACTION,
|
||||
GINT_TO_POINTER(actionitems[ii].act));
|
||||
g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
|
||||
GINT_TO_POINTER(FROM_POPUP));
|
||||
g_object_set_data(G_OBJECT(item), LIST_INDEX, GINT_TO_POINTER(index));
|
||||
g_signal_connect(G_OBJECT(item), "activate",
|
||||
G_CALLBACK(actionclick), data);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
|
||||
}
|
||||
|
||||
gtk_widget_show_all(menu);
|
||||
|
||||
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
|
||||
(NULL == event ? 0 : event->button),
|
||||
gdk_event_get_time((GdkEvent*)event));
|
||||
}
|
||||
|
||||
void
|
||||
actionclick(GtkWidget *widget, gpointer gdata) {
|
||||
struct cbdata *data = gdata;
|
||||
GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter;
|
||||
enum listact act =
|
||||
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION));
|
||||
enum listfrom from =
|
||||
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION_FROM));
|
||||
int index;
|
||||
tr_stat_t *sb;
|
||||
|
||||
if(ACT_OPEN == act) {
|
||||
makeaddwind(data);
|
||||
return;
|
||||
}
|
||||
|
||||
index = -1;
|
||||
switch(from) {
|
||||
case FROM_BUTTON:
|
||||
if(gtk_tree_selection_get_selected(sel, &model, &iter))
|
||||
gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
|
||||
MC_ROW_INDEX, &index, -1);
|
||||
/* XXX should I assert(0 <= index) to insure a row was selected? */
|
||||
break;
|
||||
case FROM_POPUP:
|
||||
index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_INDEX));
|
||||
break;
|
||||
default:
|
||||
assert(!"unknown action source");
|
||||
break;
|
||||
}
|
||||
|
||||
if(0 <= index) {
|
||||
switch(act) {
|
||||
case ACT_START:
|
||||
tr_torrentStart(data->tr, index);
|
||||
savetorrents(data->tr, data->wind, -1, NULL);
|
||||
break;
|
||||
case ACT_STOP:
|
||||
tr_torrentStop(data->tr, index);
|
||||
savetorrents(data->tr, data->wind, -1, NULL);
|
||||
break;
|
||||
case ACT_DELETE:
|
||||
/* XXX need to be able to stat just one torrent */
|
||||
if(index >= tr_torrentStat(data->tr, &sb)) {
|
||||
assert(!"XXX i'm tired");
|
||||
}
|
||||
if(TR_TORRENT_NEEDS_STOP(sb[index].status))
|
||||
tr_torrentStop(data->tr, index);
|
||||
free(sb);
|
||||
tr_torrentClose(data->tr, index);
|
||||
gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
|
||||
savetorrents(data->tr, data->wind, -1, NULL);
|
||||
break;
|
||||
case ACT_INFO:
|
||||
makeinfowind(data, index);
|
||||
break;
|
||||
default:
|
||||
assert(!"unknown type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
makeaddwind(struct cbdata *data) {
|
||||
GtkWidget *wind = gtk_file_selection_new("Add a Torrent");
|
||||
|
||||
g_object_set_data(G_OBJECT(GTK_FILE_SELECTION(wind)->ok_button),
|
||||
CBDATA_PTR, data);
|
||||
g_signal_connect(GTK_FILE_SELECTION(wind)->ok_button, "clicked",
|
||||
G_CALLBACK(fileclick), wind);
|
||||
g_signal_connect_swapped(GTK_FILE_SELECTION(wind)->cancel_button, "clicked",
|
||||
G_CALLBACK(gtk_widget_destroy), wind);
|
||||
gtk_window_set_transient_for(GTK_WINDOW(wind), GTK_WINDOW(data->wind));
|
||||
gtk_window_set_destroy_with_parent(GTK_WINDOW(wind), TRUE);
|
||||
gtk_window_set_modal(GTK_WINDOW(wind), TRUE);
|
||||
gtk_widget_show_all(wind);
|
||||
}
|
||||
|
||||
gboolean
|
||||
addtorrent(tr_handle_t *tr, GtkWidget *parentwind, const char *torrent,
|
||||
const char *dir, gboolean paused) {
|
||||
char *wd;
|
||||
|
||||
if(0 != tr_torrentInit(tr, torrent)) {
|
||||
/* XXX would be nice to have errno strings, are they printed to stdout? */
|
||||
errmsg(parentwind, "Failed to open torrent file %s", torrent);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(NULL != dir)
|
||||
tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, dir);
|
||||
else {
|
||||
/* XXX need pref for download directory */
|
||||
wd = g_new(char, MAXPATHLEN + 1);
|
||||
if(NULL == getcwd(wd, MAXPATHLEN + 1))
|
||||
tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, ".");
|
||||
else {
|
||||
tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, wd);
|
||||
free(wd);
|
||||
}
|
||||
}
|
||||
|
||||
if(!paused)
|
||||
tr_torrentStart(tr, tr_torrentCount(tr) - 1);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
fileclick(GtkWidget *widget, gpointer gdata) {
|
||||
struct cbdata *data = g_object_get_data(G_OBJECT(widget), CBDATA_PTR);
|
||||
GtkWidget *wind = gdata;
|
||||
const char *file = gtk_file_selection_get_filename(GTK_FILE_SELECTION(wind));
|
||||
|
||||
if(addtorrent(data->tr, data->wind, file, NULL, FALSE)) {
|
||||
updatemodel(data);
|
||||
savetorrents(data->tr, data->wind, -1, NULL);
|
||||
}
|
||||
|
||||
gtk_widget_destroy(wind);
|
||||
}
|
||||
|
||||
const char *
|
||||
statusstr(int status) {
|
||||
switch(status) {
|
||||
case TR_STATUS_CHECK: return "check";
|
||||
case TR_STATUS_DOWNLOAD: return "download";
|
||||
case TR_STATUS_SEED: return "seed";
|
||||
case TR_STATUS_STOPPING: return "stopping";
|
||||
case TR_STATUS_STOPPED: return "stopped";
|
||||
case TR_STATUS_PAUSE: return "pause";
|
||||
case TR_TRACKER_ERROR: return "error";
|
||||
default:
|
||||
assert(!"unknown status code");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
makeinfowind(struct cbdata *data, int index) {
|
||||
tr_stat_t *sb;
|
||||
GtkWidget *wind, *table, *name, *value;
|
||||
char buf[32];
|
||||
|
||||
if(index >= tr_torrentStat(data->tr, &sb)) {
|
||||
assert(!"XXX i'm tired");
|
||||
}
|
||||
wind = gtk_dialog_new_with_buttons(sb[index].info.name, GTK_WINDOW(data->wind),
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
|
||||
|
||||
table = gtk_table_new(21, 2, FALSE);
|
||||
|
||||
name = gtk_label_new("Torrent File");
|
||||
value = gtk_label_new(sb[index].info.torrent);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 0, 1);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 0, 1);
|
||||
name = gtk_label_new("Name");
|
||||
value = gtk_label_new(sb[index].info.name);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 1, 2);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 1, 2);
|
||||
name = gtk_label_new("Tracker Address");
|
||||
value = gtk_label_new(sb[index].info.trackerAddress);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 2, 3);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 2, 3);
|
||||
name = gtk_label_new("Tracker Port");
|
||||
snprintf(buf, sizeof buf, "%i", sb[index].info.trackerPort);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 3, 4);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 3, 4);
|
||||
name = gtk_label_new("Tracker Announce URL");
|
||||
value = gtk_label_new(sb[index].info.trackerAnnounce);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 4, 5);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 4, 5);
|
||||
name = gtk_label_new("Piece Size");
|
||||
snprintf(buf, sizeof buf, "%i", sb[index].info.pieceSize);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 5, 6);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 5, 6);
|
||||
name = gtk_label_new("Piece Count");
|
||||
snprintf(buf, sizeof buf, "%i", sb[index].info.pieceCount);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 6, 7);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 6, 7);
|
||||
name = gtk_label_new("Total Size");
|
||||
snprintf(buf, sizeof buf, "%llu", sb[index].info.totalSize);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 7, 8);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 7, 8);
|
||||
name = gtk_label_new("File Count");
|
||||
snprintf(buf, sizeof buf, "%i", sb[index].info.fileCount);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 8, 9);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 8, 9);
|
||||
name = gtk_label_new("Status");
|
||||
value = gtk_label_new(statusstr(sb[index].status));
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 9, 10);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 9, 10);
|
||||
name = gtk_label_new("Error");
|
||||
value = gtk_label_new(sb[index].error);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 10, 11);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 10, 11);
|
||||
name = gtk_label_new("Progress");
|
||||
snprintf(buf, sizeof buf, "%f", sb[index].progress);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 11, 12);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 11, 12);
|
||||
name = gtk_label_new("Download Rate");
|
||||
value = gtk_label_new(buf);
|
||||
snprintf(buf, sizeof buf, "%f", sb[index].rateDownload);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 12, 13);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 12, 13);
|
||||
name = gtk_label_new("Upload Rate");
|
||||
snprintf(buf, sizeof buf, "%f", sb[index].rateUpload);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 13, 14);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 13, 14);
|
||||
name = gtk_label_new("ETA");
|
||||
snprintf(buf, sizeof buf, "%i", sb[index].eta);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 14, 15);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 14, 15);
|
||||
name = gtk_label_new("Total Peers");
|
||||
snprintf(buf, sizeof buf, "%i", sb[index].peersTotal);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 15, 16);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 15, 16);
|
||||
name = gtk_label_new("Uploading Peers");
|
||||
snprintf(buf, sizeof buf, "%i", sb[index].peersUploading);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 16, 17);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 16, 17);
|
||||
name = gtk_label_new("Downloading Peers");
|
||||
snprintf(buf, sizeof buf, "%i", sb[index].peersDownloading);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 17, 18);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 17, 18);
|
||||
name = gtk_label_new("Downloaded");
|
||||
snprintf(buf, sizeof buf, "%llu", sb[index].downloaded);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 18, 19);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 18, 19);
|
||||
name = gtk_label_new("Uploaded");
|
||||
snprintf(buf, sizeof buf, "%llu", sb[index].uploaded);
|
||||
value = gtk_label_new(buf);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 19, 20);
|
||||
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 19, 20);
|
||||
|
||||
gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(wind)->vbox), table);
|
||||
g_signal_connect(G_OBJECT(wind), "response",
|
||||
G_CALLBACK(gtk_widget_destroy), NULL);
|
||||
gtk_widget_show_all(wind);
|
||||
free(sb);
|
||||
}
|
||||
|
||||
gboolean
|
||||
savetorrents(tr_handle_t *tr, GtkWidget *wind, int count, tr_stat_t *stat) {
|
||||
char *errstr;
|
||||
tr_stat_t *st;
|
||||
gboolean ret;
|
||||
|
||||
assert(NULL != tr || 0 <= count);
|
||||
|
||||
if(0 <= count)
|
||||
ret = cf_savestate(count, stat, &errstr);
|
||||
else {
|
||||
count = tr_torrentStat(tr, &st);
|
||||
ret = cf_savestate(count, st, &errstr);
|
||||
free(st);
|
||||
}
|
||||
|
||||
if(!ret) {
|
||||
errmsg(wind, "%s", errstr);
|
||||
g_free(errstr);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
121
gtk/util.c
Normal file
121
gtk/util.c
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
Copyright (c) 2005 Joshua Elsasser. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
static void
|
||||
errcb(GtkWidget *widget, int resp, gpointer data);
|
||||
|
||||
gboolean
|
||||
strbool(const char *str) {
|
||||
switch(str[0]) {
|
||||
case 'y':
|
||||
case 'Y':
|
||||
case '1':
|
||||
case 'j':
|
||||
case 'e':
|
||||
return TRUE;
|
||||
default:
|
||||
if(0 == g_ascii_strcasecmp("on", str))
|
||||
return TRUE;
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
errmsg(GtkWidget *wind, const char *format, ...) {
|
||||
GtkWidget *dialog;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
dialog = verrmsg(wind, NULL, NULL, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
errmsg_full(GtkWidget *wind, errfunc_t func, void *data,
|
||||
const char *format, ...) {
|
||||
GtkWidget *dialog;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
dialog = verrmsg(wind, func, data, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
verrmsg(GtkWidget *wind, errfunc_t func, void *data,
|
||||
const char *format, va_list ap) {
|
||||
GtkWidget *dialog;
|
||||
char *msg;
|
||||
GList *funcdata;
|
||||
|
||||
msg = g_strdup_vprintf(format, ap);
|
||||
|
||||
if(NULL == wind)
|
||||
dialog = gtk_message_dialog_new(
|
||||
NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg);
|
||||
else
|
||||
dialog = gtk_message_dialog_new(
|
||||
GTK_WINDOW(wind), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg);
|
||||
|
||||
if(NULL == func)
|
||||
funcdata = NULL;
|
||||
else
|
||||
funcdata = g_list_append(g_list_append(NULL, func), data);
|
||||
g_signal_connect(dialog, "response", G_CALLBACK(errcb), funcdata);
|
||||
if(NULL != wind)
|
||||
gtk_widget_show_all(dialog);
|
||||
g_free(msg);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
static void
|
||||
errcb(GtkWidget *widget, int resp SHUTUP, gpointer data) {
|
||||
GList *funcdata;
|
||||
errfunc_t func;
|
||||
|
||||
if(NULL != data) {
|
||||
funcdata = g_list_first(data);
|
||||
func = funcdata->data;
|
||||
data = funcdata->next->data;
|
||||
func(data);
|
||||
g_list_free(funcdata);
|
||||
}
|
||||
|
||||
gtk_widget_destroy(widget);
|
||||
}
|
||||
65
gtk/util.h
Normal file
65
gtk/util.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright (c) 2005 Joshua Elsasser. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef TG_UTIL_H
|
||||
#define TG_UTIL_H
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
/* macro to shut up "unused parameter" warnings */
|
||||
#ifdef __GNUC__
|
||||
#define SHUTUP __attribute__((unused))
|
||||
#else
|
||||
#define SHUTUP
|
||||
#endif
|
||||
|
||||
gboolean
|
||||
strbool(const char *str);
|
||||
|
||||
typedef void (*errfunc_t)(void*);
|
||||
|
||||
/* if wind is NULL then you must call gtk_widget_show on the returned widget */
|
||||
|
||||
GtkWidget *
|
||||
errmsg(GtkWidget *wind, const char *format, ...)
|
||||
#ifdef __GNUC__
|
||||
__attribute__ ((format (printf, 2, 3)))
|
||||
#endif
|
||||
;
|
||||
|
||||
GtkWidget *
|
||||
errmsg_full(GtkWidget *wind, errfunc_t func, void *data,
|
||||
const char *format, ...)
|
||||
#ifdef __GNUC__
|
||||
__attribute__ ((format (printf, 4, 5)))
|
||||
#endif
|
||||
;
|
||||
|
||||
GtkWidget *
|
||||
verrmsg(GtkWidget *wind, errfunc_t func, void *data,
|
||||
const char *format, va_list ap);
|
||||
|
||||
#endif /* TG_UTIL_H */
|
||||
Reference in New Issue
Block a user