c71b9a1ff5d0c2b2438e2ed4179fe3b3837d7ead
[doldaconnect.git] / clients / gtk2 / dolcon.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
4  *  
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19
20 /* This file is a complete and total mess, mostly because of my
21  * inability to structure GUI programs properly. Looking at it too
22  * closely may cause ocular hemorrhaging. */
23
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include <stdarg.h>
29 #include <gtk/gtk.h>
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <doldaconnect/uilib.h>
34 #include <doldaconnect/uimisc.h>
35 #include <doldaconnect/utils.h>
36 #include <errno.h>
37 #include <regex.h>
38 #include <signal.h>
39 #include <time.h>
40 #include <sys/time.h>
41 #include <pwd.h>
42 #include <locale.h>
43 #include <assert.h>
44
45 #ifdef HAVE_CONFIG_H
46 #include <config.h>
47 #endif
48 #include "dolcon.h"
49 #include "hublist.h"
50 #include "progressbar.h"
51
52 #define TRHISTSIZE 10
53
54 struct trdata
55 {
56     size_t poshist[TRHISTSIZE];
57     double timehist[TRHISTSIZE];
58     int hc;
59 };
60
61 struct fndata
62 {
63     GtkTextBuffer *textbuf;
64 };
65
66 struct srchsize
67 {
68     int size;
69     int num;
70     int slots;
71     double resptime;
72     GtkTreeRowReference *ref;
73 };
74
75 struct knownspeed
76 {
77     char *userid;
78     int speed, seq;
79     time_t fetched;
80 };
81
82 GtkWidget *inpdialog;
83 GtkListStore *fnmodel, *ulmodel, *dlmodel, *reslist;
84 GtkTreeStore *srchmodel;
85 GtkTreeModelFilter *srchmodelfilter;
86 GtkTextTagTable *chattags;
87 int dcfd = -1, gdkread = -1, gdkwrite = -1;
88 int curchat = -1;
89 char *pubhubaddr = NULL;
90 char *connectas = NULL;
91 char *dcserver = NULL;
92 int autoconn = 1;
93 int srchautoupdate = 0;
94 int cursrch = -1, nextsrch = -1;
95 time_t srcheta;
96 struct srchsize *srchsizes = NULL;
97 struct knownspeed *knownspeeds = NULL;
98 int numsizes = 0, numspeeds = 0, ksqueryseq = -1, ksquerytag = -1, lsrestag = -1;
99
100 void dcfdcallback(gpointer data, gint source, GdkInputCondition condition);
101 void srchstatupdate(void);
102
103 #include "mainwnd.gtkh"
104 #include "inpdialog.gtkh"
105 #include "pref.gtkh"
106 #include "reslist.gtkh"
107
108 void updatewrite(void)
109 {
110     if(dcfd < 0)
111         return;
112     if(dc_wantwrite())
113     {
114         if(gdkwrite == -1)
115             gdkwrite = gdk_input_add(dcfd, GDK_INPUT_WRITE, dcfdcallback, NULL);
116     } else {
117         if(gdkwrite != -1)
118         {
119             gdk_input_remove(gdkwrite);
120             gdkwrite = -1;
121         }
122     }
123 }
124
125 double ntime(void)
126 {
127     struct timeval tv;
128     
129     gettimeofday(&tv, NULL);
130     return((double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0));
131 }
132
133 void fndestroycb(struct dc_fnetnode *fn)
134 {
135     struct fndata *data;
136     GtkTextBuffer *textbuf;
137     
138     data = fn->udata;
139     g_object_unref(data->textbuf);
140     free(data);
141     if(curchat == fn->id)
142     {
143         textbuf = gtk_text_buffer_new(chattags);
144         gtk_text_view_set_buffer(GTK_TEXT_VIEW(main_chatview), textbuf);
145         g_object_unref(textbuf);
146     }
147 }
148
149 void addfndata(struct dc_fnetnode *fn)
150 {
151     struct fndata *data;
152     
153     if(fn->udata != NULL)
154         return;
155     fn->destroycb = fndestroycb;
156     data = smalloc(sizeof(*data));
157     data->textbuf = gtk_text_buffer_new(chattags);
158     fn->udata = data;
159 }
160
161 void trdestroycb(struct dc_transfer *tr)
162 {
163     free(tr->udata);
164 }
165
166 void addtrdata(struct dc_transfer *tr)
167 {
168     struct trdata *data;
169     
170     if(tr->udata != NULL)
171         return;
172     tr->destroycb = trdestroycb;
173     data = smalloc(sizeof(*data));
174     memset(data, 0, sizeof(*data));
175     tr->udata = data;
176 }
177
178 void updatetrdata(struct dc_transfer *tr)
179 {
180     int i;
181     struct trdata *data;
182     
183     data = tr->udata;
184     if(data->hc < TRHISTSIZE)
185     {
186         data->poshist[data->hc] = tr->curpos;
187         data->timehist[data->hc] = ntime();
188         data->hc++;
189     } else {
190         for(i = 0; i < TRHISTSIZE - 1; i++)
191         {
192             data->poshist[i] = data->poshist[i + 1];
193             data->timehist[i] = data->timehist[i + 1];
194         }
195         data->poshist[i] = tr->curpos;
196         data->timehist[i] = ntime();
197     }
198 }
199
200 char *getfnstatestock(int state)
201 {
202     if(state == DC_FNN_STATE_SYN)
203         return("gtk-jump-to");
204     if(state == DC_FNN_STATE_HS)
205         return("gtk-execute");
206     if(state == DC_FNN_STATE_EST)
207         return("gtk-yes");
208     if(state == DC_FNN_STATE_DEAD)
209         return("gtk-cancel");
210     return(NULL);
211 }
212
213 void updatehublist(void)
214 {
215     int done;
216     struct dc_fnetnode *fn;
217     GtkTreeIter iter;
218     int id;
219     char *buf;
220     char *name;
221     int state, numusers;
222     
223     for(fn = dc_fnetnodes; fn != NULL; fn = fn->next)
224         fn->found = 0;
225     done = 0;
226     while(!done)
227     {
228         done = 1;
229         if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(fnmodel), &iter))
230         {
231             do
232             {
233                 gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1);
234                 if((fn = dc_findfnetnode(id)) == NULL)
235                 {
236                     /* I can't seem to get a sensible reply fromp
237                      * gtk_list_store, so I'm just doing this
238                      * instead. */
239                     gtk_list_store_remove(fnmodel, &iter);
240                     done = 0;
241                     break;
242                 } else {
243                     gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 1, &name, 2, &state, 3, &numusers, -1);
244                     if(fn->name == NULL)
245                         buf = _("Unknown");
246                     else
247                         buf = icswcstombs(fn->name, "UTF-8", NULL);
248                     if(strcmp(buf, name))
249                         gtk_list_store_set(fnmodel, &iter, 1, buf, -1);
250                     if(state != fn->state)
251                     {
252                         gtk_list_store_set(fnmodel, &iter, 2, fn->state, -1);
253                         gtk_list_store_set(fnmodel, &iter, 4, getfnstatestock(fn->state), -1);
254                     }
255                     if(numusers != fn->numusers)
256                         gtk_list_store_set(fnmodel, &iter, 3, fn->numusers, -1);
257                     g_free(name);
258                     fn->found = 1;
259                 }
260             } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(fnmodel), &iter));
261         }
262     }
263     for(fn = dc_fnetnodes; fn != NULL; fn = fn->next)
264     {
265         if(!fn->found)
266         {
267             if(fn->name == NULL)
268                 buf = _("Unknown");
269             else
270                 buf = icswcstombs(fn->name, "UTF-8", NULL);
271             gtk_list_store_append(fnmodel, &iter);
272             gtk_list_store_set(fnmodel, &iter, 0, fn->id, 1, buf, 2, fn->state, 3, fn->numusers, 4, getfnstatestock(fn->state), -1);
273             addfndata(fn);
274         }
275     }
276 }
277
278 char *bytes2si(long long bytes)
279 {
280     int i;
281     double b;
282     static char ret[64];
283     static char pfx[] = {'k', 'M', 'G', 'T'};
284     
285     b = bytes;
286     for(i = 0; (b >= 1024) && (i < sizeof(pfx)); i++)
287         b /= 1024;
288     if(i == 0)
289         snprintf(ret, 64, "%.1f B", b);
290     else
291         snprintf(ret, 64, "%.1f %ciB", b, pfx[i - 1]);
292     return(ret);
293 }
294
295 void percentagefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
296 {
297     int colnum;
298     float val;
299     char buf[64];
300     
301     colnum = GPOINTER_TO_INT(data);
302     gtk_tree_model_get(model, iter, colnum, &val, -1);
303     snprintf(buf, 64, "%.2f%%", (double)(val * 100.0));
304     g_object_set(rend, "text", buf, NULL);
305 }
306
307 void transnicebytefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
308 {
309     int colnum, val;
310     char buf[64];
311     
312     colnum = GPOINTER_TO_INT(data);
313     gtk_tree_model_get(model, iter, colnum, &val, -1);
314     if(val >= 0)
315         snprintf(buf, 64, "%'i", val);
316     else
317         strcpy(buf, _("Unknown"));
318     g_object_set(rend, "text", buf, NULL);
319 }
320
321 void transnicebytefunc2(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
322 {
323     int colnum;
324     long long val;
325     char buf[64];
326     
327     colnum = GPOINTER_TO_INT(data);
328     gtk_tree_model_get(model, iter, colnum, &val, -1);
329     if(val >= 0)
330         strcpy(buf, bytes2si(val));
331     else
332         strcpy(buf, _("Unknown"));
333     g_object_set(rend, "text", buf, NULL);
334 }
335
336 void hidezerofunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
337 {
338     int colnum, val;
339     char buf[64];
340     
341     colnum = GPOINTER_TO_INT(data);
342     gtk_tree_model_get(model, iter, colnum, &val, -1);
343     if(val > 0)
344         snprintf(buf, 64, "%i", val);
345     else
346         strcpy(buf, "");
347     g_object_set(rend, "text", buf, NULL);
348 }
349
350 void speedtimefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
351 {
352     int speed, size, time;
353     char buf[64];
354     
355     gtk_tree_model_get(model, iter, 4, &size, 8, &speed, -1);
356     if(speed > 0)
357     {
358         time = (size / speed) / 60;
359         if(time < 1)
360             snprintf(buf, 64, "%'i (<00:01)", speed);
361         else
362             snprintf(buf, 64, "%'i (%02i:%02i)", speed, time / 60, time % 60);
363     } else if(speed == 0) {
364         strcpy(buf, "0");
365     } else {
366         strcpy(buf, _("Unknown"));
367     }
368     g_object_set(rend, "text", buf, NULL);
369 }
370
371 void transspeedinfo(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
372 {
373     int id;
374     struct dc_transfer *tr;
375     struct trdata *d;
376     char buf[64];
377     int speed;
378     
379     gtk_tree_model_get(model, iter, 0, &id, -1);
380     if((tr = dc_findtransfer(id)) != NULL)
381     {
382         d = tr->udata;
383         if((tr->state != DC_TRNS_MAIN) || (d == NULL))
384         {
385             buf[0] = 0;
386         } else if(d->hc < 2) {
387             strcpy(buf, "...");
388         } else {
389             speed = (((double)(d->poshist[d->hc - 1] - d->poshist[0])) / (d->timehist[d->hc - 1] - d->timehist[0]));
390             snprintf(buf, 64, "%s/s", bytes2si(speed));
391         }
392     } else {
393         buf[0] = 0;
394     }
395     g_object_set(rend, "text", buf, NULL);
396 }
397
398 void transerrorinfo(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
399 {
400     int error;
401     time_t errortime;
402     char finbuf[64], tbuf[64], *errstr;
403     
404     gtk_tree_model_get(model, iter, 10, &error, 11, &errortime, -1);
405     if(error != DC_TRNSE_NOERROR)
406     {
407         if(error == DC_TRNSE_NOTFOUND)
408             errstr = _("Not found");
409         else if(error == DC_TRNSE_NOSLOTS)
410             errstr = _("No slots");
411         strftime(tbuf, 64, _("%H:%M:%S"), localtime(&errortime));
412         snprintf(finbuf, 64, _("%s (reported at %s)"), errstr, tbuf);
413     } else {
414         *finbuf = 0;
415     }
416     g_object_set(rend, "text", finbuf, NULL);
417 }
418
419 char *gettrstatestock(int state)
420 {
421     if(state == DC_TRNS_WAITING)
422         return("gtk-jump-to");
423     if(state == DC_TRNS_HS)
424         return("gtk-execute");
425     if(state == DC_TRNS_MAIN)
426         return("gtk-network");
427     if(state == DC_TRNS_DONE)
428         return("gtk-yes");
429     return(NULL);
430 }
431
432 gint updatetransfers(gpointer data)
433 {
434     struct dc_transfer *tr;
435     struct trdata *d;
436     double now;
437     
438     now = ntime();
439     for(tr = dc_transfers; tr != NULL; tr = tr->next)
440     {
441         if((d = tr->udata) != NULL)
442         {
443             if((d->hc > 0) && ((now - d->timehist[d->hc - 1]) > 2))
444                 updatetrdata(tr);
445         }
446     }
447     return(TRUE);
448 }
449
450 void updatetransferlists(void)
451 {
452     int i;
453     int done;
454     struct dc_transfer *transfer;
455     GtkTreeIter iter;
456     int id;
457     char *buf;
458     char *peerid, *peernick, *path, *hash;
459     int state, dir, size, curpos, error;
460     time_t errortime;
461     GtkListStore *stores[3];
462     
463     for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next)
464         transfer->found = 0;
465     stores[DC_TRNSD_UNKNOWN] = NULL;
466     stores[DC_TRNSD_UP] = ulmodel;
467     stores[DC_TRNSD_DOWN] = dlmodel;
468     for(i = 0; i < 3; i++)
469     {
470         if(stores[i] == NULL)
471             continue;
472         done = 0;
473         while(!done)
474         {
475             done = 1;
476             if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(stores[i]), &iter))
477             {
478                 do
479                 {
480                     gtk_tree_model_get(GTK_TREE_MODEL(stores[i]), &iter, 0, &id, 1, &dir, -1);
481                     if(((transfer = dc_findtransfer(id)) == NULL) || (transfer->dir != dir))
482                     {
483                         gtk_list_store_remove(stores[i], &iter);
484                         done = 0;
485                         break;
486                     } else {
487                         transfer->found = 1;
488                         gtk_tree_model_get(GTK_TREE_MODEL(stores[i]), &iter, 2, &state, 3, &peerid, 4, &peernick, 5, &path, 6, &size, 7, &curpos, 10, &error, 11, &errortime, 12, &hash, -1);
489                         if(state != transfer->state)
490                             gtk_list_store_set(stores[i], &iter, 2, transfer->state, 8, gettrstatestock(transfer->state), -1);
491                         if(size != transfer->size)
492                             gtk_list_store_set(stores[i], &iter, 6, transfer->size, -1);
493                         if(curpos != transfer->curpos)
494                         {
495                             gtk_list_store_set(stores[i], &iter, 7, transfer->curpos, -1);
496                             if(transfer->udata != NULL)
497                                 updatetrdata(transfer);
498                         }
499                         if(error != transfer->error)
500                             gtk_list_store_set(stores[i], &iter, 10, transfer->error, -1);
501                         if(errortime != transfer->errortime)
502                             gtk_list_store_set(stores[i], &iter, 11, transfer->errortime, -1);
503                         if((transfer->size > 0) && (transfer->curpos > 0))
504                             gtk_list_store_set(stores[i], &iter, 9, (float)transfer->curpos / (float)transfer->size, -1);
505                         buf = icswcstombs(transfer->peerid, "UTF-8", NULL);
506                         if(strcmp(buf, peerid))
507                             gtk_list_store_set(stores[i], &iter, 3, buf, -1);
508                         buf = icswcstombs(((transfer->peernick == NULL) || (transfer->peernick[0] == L'\0'))?transfer->peerid:transfer->peernick, "UTF-8", NULL);
509                         if(strcmp(buf, peernick))
510                             gtk_list_store_set(stores[i], &iter, 4, buf, -1);
511                         buf = (transfer->path == NULL)?_("Unknown"):icswcstombs(transfer->path, "UTF-8", NULL);
512                         if(strcmp(buf, path))
513                             gtk_list_store_set(stores[i], &iter, 5, buf, -1);
514                         buf = (transfer->hash == NULL)?"":icswcstombs(transfer->hash, "UTF-8", NULL);
515                         if(strcmp(buf, path))
516                             gtk_list_store_set(stores[i], &iter, 12, buf, -1);
517                         g_free(hash);
518                         g_free(peerid);
519                         g_free(peernick);
520                         g_free(path);
521                     }
522                 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(stores[i]), &iter));
523             }
524         }
525     }
526     for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next)
527     {
528         if(!transfer->found)
529         {
530             if(stores[transfer->dir] != NULL)
531             {
532                 peerid = icwcstombs(transfer->peerid, "UTF-8");
533                 peernick = icwcstombs(((transfer->peernick == NULL) || (transfer->peernick[0] == L'\0'))?transfer->peerid:transfer->peernick, "UTF-8");
534                 path = (transfer->path == NULL)?_("Unknown"):icwcstombs(transfer->path, "UTF-8");
535                 hash = (transfer->hash == NULL)?"":icwcstombs(transfer->hash, "UTF-8");
536                 gtk_list_store_append(stores[transfer->dir], &iter);
537                 gtk_list_store_set(stores[transfer->dir], &iter,
538                                    0, transfer->id,
539                                    1, transfer->dir,
540                                    2, transfer->state,
541                                    3, peerid,
542                                    4, peernick,
543                                    5, path,
544                                    6, transfer->size,
545                                    7, transfer->curpos,
546                                    8, gettrstatestock(transfer->state),
547                                    9, 0.0,
548                                    10, transfer->error,
549                                    11, transfer->errortime,
550                                    12, hash,
551                                    -1);
552                 free(peerid);
553                 free(peernick);
554                 if(transfer->path != NULL)
555                     free(path);
556                 if(transfer->hash != NULL)
557                     free(hash);
558                 addtrdata(transfer);
559             }
560         }
561     }
562 }
563
564 void updatesbar(char *msg)
565 {
566     gtk_statusbar_pop(GTK_STATUSBAR(main_statusbar), 0);
567     gtk_statusbar_push(GTK_STATUSBAR(main_statusbar), 0, msg);
568 }
569
570 void freesrchsizes(void)
571 {
572     int i;
573     
574     for(i = 0; i < numsizes; i++)
575     {
576         if(srchsizes[i].ref != NULL)
577             gtk_tree_row_reference_free(srchsizes[i].ref);
578     }
579     if(srchsizes != NULL)
580         free(srchsizes);
581     srchsizes = NULL;
582     numsizes = 0;
583 }
584
585 void dcdisconnected(void)
586 {
587     if(gdkread != -1)
588     {
589         gdk_input_remove(gdkread);
590         gdkread = -1;
591     }
592     dcfd = -1;
593     updatehublist();
594     updatetransferlists();
595     cursrch = nextsrch = -1;
596     gtk_tree_store_clear(srchmodel);
597     freesrchsizes();
598     gtk_widget_set_sensitive(main_connmenu, TRUE);
599     gtk_widget_set_sensitive(main_dconnmenu, FALSE);
600     gtk_widget_set_sensitive(main_simplesrch, TRUE);
601     gtk_widget_set_sensitive(main_realsrch, TRUE);
602     gtk_widget_set_sensitive(main_srchbtn, TRUE);
603     gtk_widget_set_sensitive(main_srchcanbtn, FALSE);
604     updatesbar(_("Disconnected"));
605 }
606
607 char *inputbox(char *title, char *prompt, char *def, int echo)
608 {
609     int resp;
610     GtkWidget *swnd;
611     char *buf;
612     
613     inpdialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(main_wnd), GTK_DIALOG_MODAL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
614     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(inpdialog)->vbox), swnd = create_inpdialog_wnd(), TRUE, TRUE, 0);
615     gtk_widget_show(swnd);
616     if(!echo)
617         gtk_entry_set_visibility(GTK_ENTRY(inpdialog_entry), FALSE);
618     gtk_label_set_text(GTK_LABEL(inpdialog_prompt), prompt);
619     gtk_entry_set_text(GTK_ENTRY(inpdialog_entry), def);
620     resp = gtk_dialog_run(GTK_DIALOG(inpdialog));
621     if(!echo)
622         gtk_entry_set_visibility(GTK_ENTRY(inpdialog_entry), TRUE);
623     if(resp == GTK_RESPONSE_ACCEPT)
624         buf = strdup(gtk_entry_get_text(GTK_ENTRY(inpdialog_entry)));
625     else
626         buf = NULL;
627     gtk_widget_destroy(inpdialog);
628     updatewrite();
629     return(buf);
630 }
631
632 int msgbox(int type, int buttons, char *format, ...)
633 {
634     GtkWidget *swnd;
635     va_list args;
636     char *buf;
637     int resp;
638     
639     va_start(args, format);
640     buf = vsprintf2(format, args);
641     va_end(args);
642     swnd = gtk_message_dialog_new(GTK_WINDOW(main_wnd), GTK_DIALOG_MODAL, type, buttons, "%s", buf);
643     resp = gtk_dialog_run(GTK_DIALOG(swnd));
644     gtk_widget_destroy(swnd);
645     free(buf);
646     return(resp);
647 }
648
649 void readconfigfile(void)
650 {
651     FILE *cfgfile;
652     char *homedir, *buf, *p;
653     int w, h;
654     
655     if((homedir = getenv("HOME")) == NULL)
656     {
657         fprintf(stderr, "warning: could not find home directory!\n");
658         return;
659     }
660     buf = sprintf2("%s/.dolconrc", homedir);
661     if((cfgfile = fopen(buf, "r")) == NULL)
662     {
663         if(errno != ENOENT)
664             perror(buf);
665         free(buf);
666         return;
667     }
668     free(buf);
669     buf = smalloc(1024);
670     while(fgets(buf, 1024, cfgfile) != NULL)
671     {
672         if(strlen(buf) < 1)
673             continue;
674         p = buf + strlen(buf);
675         if(p[-1] == '\n')
676             *(--p) = 0;
677         if((p = strchr(buf, ':')) == NULL)
678             continue;
679         *(p++) = 0;
680         while((*p == ' ') || (*p == '\t'))
681             p++;
682         if(!strcmp(buf, "wnd-width"))
683         {
684             w = atoi(p);
685         } else if(!strcmp(buf, "wnd-height")) {
686             h = atoi(p);
687         } else if(!strcmp(buf, "pane1-pos")) {
688             gtk_paned_set_position(GTK_PANED(main_pane1), atoi(p));
689         } else if(!strcmp(buf, "pane2-pos")) {
690             gtk_paned_set_position(GTK_PANED(main_pane2), atoi(p));
691         } else if(!strcmp(buf, "pane3-pos")) {
692             gtk_paned_set_position(GTK_PANED(main_pane3), atoi(p));
693         } else if(!strcmp(buf, "pubhubaddr")) {
694             free(pubhubaddr);
695             pubhubaddr = sstrdup(p);
696         } else if(!strcmp(buf, "dcserver")) {
697             free(dcserver);
698             dcserver = sstrdup(p);
699         } else if(!strcmp(buf, "advexpanded")) {
700             gtk_expander_set_expanded(GTK_EXPANDER(main_advexp), atoi(p));
701         } else if(!strcmp(buf, "connectas")) {
702             free(connectas);
703             connectas = sstrdup(p);
704         } else if(!strcmp(buf, "autoconn")) {
705             autoconn = atoi(p);
706         } else if(!strcmp(buf, "filternoslots")) {
707             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(main_filternoslots), atoi(p));
708         }
709     }
710     free(buf);
711     fclose(cfgfile);
712 /*
713     if(w != 1589)
714         abort();
715 */
716     gtk_window_resize(GTK_WINDOW(main_wnd), w, h);
717 }
718
719 void updateconfigfile(void)
720 {
721     FILE *cfgfile;
722     char *homedir, *buf;
723     int w, h;
724     
725     if((homedir = getenv("HOME")) == NULL)
726     {
727         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not get your home directory!"));
728         return;
729     }
730     buf = sprintf2("%s/.dolconrc", homedir);
731     if((cfgfile = fopen(buf, "w")) == NULL)
732     {
733         free(buf);
734         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not open configuration file for writing: %s"), strerror(errno));
735         return;
736     }
737     free(buf);
738     gtk_window_get_size(GTK_WINDOW(main_wnd), &w, &h);
739     fprintf(cfgfile, "wnd-width: %i\n", w);
740     fprintf(cfgfile, "wnd-height: %i\n", h);
741     fprintf(cfgfile, "pane1-pos: %i\n", gtk_paned_get_position(GTK_PANED(main_pane1)));
742     fprintf(cfgfile, "pane2-pos: %i\n", gtk_paned_get_position(GTK_PANED(main_pane2)));
743     fprintf(cfgfile, "pane3-pos: %i\n", gtk_paned_get_position(GTK_PANED(main_pane3)));
744     fprintf(cfgfile, "pubhubaddr: %s\n", pubhubaddr);
745     fprintf(cfgfile, "dcserver: %s\n", dcserver);
746     fprintf(cfgfile, "advexpanded: %i\n", gtk_expander_get_expanded(GTK_EXPANDER(main_advexp)));
747     fprintf(cfgfile, "connectas: %s\n", connectas);
748     fprintf(cfgfile, "autoconn: %i\n", autoconn);
749     fprintf(cfgfile, "filternoslots: %i\n", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(main_filternoslots)));
750     fclose(cfgfile);
751 }
752
753 gboolean initdeath(GtkWidget *widget, gpointer data)
754 {
755     updateconfigfile();
756     gtk_main_quit();
757     return(TRUE);
758 }
759
760 void cb_inpdialog_entry_activate(GtkWidget *widget, gpointer data)
761 {
762     gtk_dialog_response(GTK_DIALOG(inpdialog), GTK_RESPONSE_ACCEPT);
763 }
764
765 int loginconv(int type, wchar_t *prompt, char **resp, void *data)
766 {
767     int ret;
768     char *buf;
769     
770     ret = 0;
771     buf = icwcstombs(prompt, "UTF-8");
772     switch(type)
773     {
774     case DC_LOGIN_CONV_NOECHO:
775         if((*resp = inputbox(_("Login"), buf, "", 0)) == NULL)
776             ret = 1;
777         break;
778     case DC_LOGIN_CONV_ECHO:
779         if((*resp = inputbox(_("Login"), buf, "", 1)) == NULL)
780             ret = 1;
781         break;
782     case DC_LOGIN_CONV_INFO:
783         msgbox(GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "%s", buf);
784         break;
785     case DC_LOGIN_CONV_ERROR:
786         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", buf);
787         break;
788     }
789     free(buf);
790     updatewrite();
791     return(ret);
792 }
793
794 void getfnlistcallback(int resp, void *data)
795 {
796     updatehublist();
797 }
798
799 void gettrlistcallback(int resp, void *data)
800 {
801     updatetransferlists();
802 }
803
804 void logincallback(int err, wchar_t *reason, void *data)
805 {
806     switch(err)
807     {
808     case DC_LOGIN_ERR_SUCCESS:
809         dc_queuecmd(NULL, NULL, L"notify", L"all", L"on", NULL);
810         dc_getfnlistasync(getfnlistcallback, NULL);
811         dc_gettrlistasync(gettrlistcallback, NULL);
812         updatesbar("Authenticated");
813         break;
814     case DC_LOGIN_ERR_NOLOGIN:
815         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not negotiate an acceptable authentication mechanism"));
816         dc_disconnect();
817         dcdisconnected();
818         break;
819     case DC_LOGIN_ERR_SERVER:
820         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server has encountered an error"));
821         dc_disconnect();
822         dcdisconnected();
823         break;
824     case DC_LOGIN_ERR_USER:
825         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Internal client error"));
826         dc_disconnect();
827         dcdisconnected();
828         break;
829     case DC_LOGIN_ERR_CONV:
830         dc_disconnect();
831         dcdisconnected();
832         break;
833     case DC_LOGIN_ERR_AUTHFAIL:
834         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Login attempt failed!"));
835         dc_disconnect();
836         dcdisconnected();
837         break;
838     }
839     updatewrite();
840 }
841
842 GtkTreeIter *ref2iter(GtkTreeRowReference *ref)
843 {
844     static GtkTreeIter iter;
845     GtkTreePath *path;
846     
847     assert((path = gtk_tree_row_reference_get_path(ref)) != NULL);
848     assert(gtk_tree_model_get_iter(GTK_TREE_MODEL(srchmodel), &iter, path));
849     gtk_tree_path_free(path);
850     return(&iter);
851 }
852
853 GtkTreeRowReference *iter2ref(GtkTreeIter *iter)
854 {
855     GtkTreePath *path;
856     GtkTreeRowReference *ref;
857     
858     assert((path = gtk_tree_model_get_path(GTK_TREE_MODEL(srchmodel), iter)) != NULL);
859     assert((ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(srchmodel), path)) != NULL);
860     gtk_tree_path_free(path);
861     return(ref);
862 }
863
864 struct srchsize *finddiscsize(void)
865 {
866     int i;
867     GtkTreeIter iter;
868     
869     for(i = 0; i < numsizes; i++)
870     {
871         if(srchsizes[i].size == -1)
872             return(&srchsizes[i]);
873     }
874     srchsizes = srealloc(srchsizes, sizeof(*srchsizes) * ++numsizes);
875     srchsizes[i].size = -1;
876     srchsizes[i].num = 1;
877     srchsizes[i].slots = 0;
878     srchsizes[i].resptime = 0.0;
879     gtk_tree_store_append(srchmodel, &iter, NULL);
880     gtk_tree_store_set(srchmodel, &iter, 3, _("Discrete sizes"), 7, 1, -1);
881     srchsizes[i].ref = iter2ref(&iter);
882     return(&srchsizes[i]);
883 }
884
885 struct knownspeed *findksentbyname(char *userid)
886 {
887     int i;
888     
889     for(i = 0; i < numspeeds; i++)
890     {
891         if(!strcmp(knownspeeds[i].userid, userid))
892             return(&knownspeeds[i]);
893     }
894     return(NULL);
895 }
896
897 struct knownspeed *findksentbyseq(int seq)
898 {
899     int i;
900     
901     for(i = 0; i < numspeeds; i++)
902     {
903         if(knownspeeds[i].seq == seq)
904             return(&knownspeeds[i]);
905     }
906     return(NULL);
907 }
908
909 gboolean ksupdaterow(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
910 {
911     struct knownspeed *ks;
912     char *userid;
913     
914     gtk_tree_model_get(GTK_TREE_MODEL(model), iter, 1, &userid, -1);
915     if(userid == NULL)
916         return(FALSE);
917     ks = findksentbyname(userid);
918     if(ks == NULL)
919     {
920         knownspeeds = srealloc(knownspeeds, (numspeeds + 1) * sizeof(*knownspeeds));
921         ks = &knownspeeds[numspeeds];
922         numspeeds++;
923         ks->userid = sstrdup(userid);
924         ks->speed = -1;
925         ks->seq = -2;
926         ksqueryseq = -2;
927     }
928     g_free(userid);
929     if(ks->speed != -1)
930         gtk_tree_store_set(GTK_TREE_STORE(model), iter, 8, ks->speed, -1);
931     return(FALSE);
932 }
933
934 gint ksupdatecb(gpointer data)
935 {
936     int i, oldnum;
937     time_t now;
938     wchar_t **users, *buf;
939     size_t userssize, usersdata;
940     
941     if(ksquerytag != -1)
942         return(TRUE);
943     now = time(NULL);
944     oldnum = numspeeds;
945     for(i = 0; i < numspeeds;)
946     {
947         if(now - knownspeeds[i].fetched > 60)
948         {
949             free(knownspeeds[i].userid);
950             memmove(&knownspeeds[i], &knownspeeds[i + 1], (--numspeeds - i) * sizeof(*knownspeeds));
951         } else {
952             i++;
953         }
954     }
955     if(oldnum != numspeeds)
956     {
957         if(numspeeds == 0)
958         {
959             free(knownspeeds);
960             knownspeeds = NULL;
961         } else {
962             knownspeeds = srealloc(knownspeeds, numspeeds * sizeof(*knownspeeds));
963         }
964     }
965     gtk_tree_model_foreach(GTK_TREE_MODEL(srchmodel), ksupdaterow, NULL);
966     if(ksqueryseq == -2)
967     {
968         users = NULL;
969         userssize = usersdata = 0;
970         ksqueryseq = 0;
971         for(i = 0; i < numspeeds; i++)
972         {
973             if(knownspeeds[i].seq == -2)
974             {
975                 assert((buf = icmbstowcs(knownspeeds[i].userid, "UTF-8")) != NULL);
976                 knownspeeds[i].seq = ksqueryseq++;
977                 addtobuf(users, buf);
978             }
979         }
980         addtobuf(users, NULL);
981         ksquerytag = dc_queuecmd(NULL, NULL, L"filtercmd", L"userspeeda", L"%a", users, NULL);
982         dc_freewcsarr(users);
983     }
984     return(TRUE);
985 }
986
987 void handleresps(void)
988 {
989     int i;
990     struct dc_response *resp;
991     struct dc_intresp *ires;
992     struct dc_fnetnode *fn;
993     struct fndata *fndata;
994     GtkTextIter iter;
995     GtkTreeIter titer, piter;
996     char *buf, *p;
997     int tosbuf;
998     struct srchsize *ss;
999     struct knownspeed *ks;
1000     
1001     while((resp = dc_getresp()) != NULL)
1002     {
1003         if(!wcscmp(resp->cmdname, L".connect"))
1004         {
1005             if(resp->code != 201)
1006             {
1007                 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server refused the connection"));
1008                 dc_disconnect();
1009                 dcdisconnected();
1010             } else if(dc_checkprotocol(resp, DC_LATEST)) {
1011                 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Server protocol revision mismatch"));
1012                 dc_disconnect();
1013                 dcdisconnected();
1014             } else {
1015                 tosbuf = 0x10; /* Minimum delay */
1016                 setsockopt(dcfd, IPPROTO_IP, IP_TOS, &tosbuf, sizeof(tosbuf));
1017                 updatesbar(_("Connected"));
1018                 dc_loginasync(connectas, 1, loginconv, logincallback, NULL);
1019             }
1020         } else if(!wcscmp(resp->cmdname, L".notify")) {
1021             dc_uimisc_handlenotify(resp);
1022             switch(resp->code)
1023             {
1024             case 600:
1025                 if((ires = dc_interpret(resp)) != NULL)
1026                 {
1027                     if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
1028                     {
1029                         fndata = fn->udata;
1030                         gtk_text_buffer_get_end_iter(fndata->textbuf, &iter);
1031                         if((buf = icwcstombs(ires->argv[3].val.str, "UTF-8")) != NULL)
1032                         {
1033                             gtk_text_buffer_insert_with_tags_by_name(fndata->textbuf, &iter, "<", -1, "sender", NULL);
1034                             gtk_text_buffer_insert_with_tags_by_name(fndata->textbuf, &iter, buf, -1, "sender", NULL);
1035                             gtk_text_buffer_insert_with_tags_by_name(fndata->textbuf, &iter, ">", -1, "sender", NULL);
1036                             gtk_text_buffer_insert(fndata->textbuf, &iter, " ", -1);
1037                             free(buf);
1038                         }
1039                         if((buf = icwcstombs(ires->argv[4].val.str, "UTF-8")) != NULL)
1040                         {
1041                             gtk_text_buffer_insert(fndata->textbuf, &iter, buf, -1);
1042                             gtk_text_buffer_insert(fndata->textbuf, &iter, "\n", -1);
1043                             free(buf);
1044                             if(curchat == fn->id)
1045                                 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(main_chatview), &iter, 0, 0, 0, 0);
1046                         }
1047                     }
1048                     dc_freeires(ires);
1049                 }
1050                 break;
1051             case 601:
1052             case 602:
1053             case 603:
1054             case 604:
1055             case 605:
1056                 updatehublist();
1057                 break;
1058             case 610:
1059             case 611:
1060             case 612:
1061             case 613:
1062             case 614:
1063             case 615:
1064             case 616:
1065             case 617:
1066             case 618:
1067                 updatetransferlists();
1068                 break;
1069             case 620:
1070                 if((ires = dc_interpret(resp)) != NULL)
1071                 {
1072                     if(ires->argv[0].val.num == nextsrch)
1073                         srcheta = time(NULL) + ires->argv[0].val.num;
1074                     dc_freeires(ires);
1075                 }
1076                 break;
1077             case 621:
1078                 if((ires = dc_interpret(resp)) != NULL)
1079                 {
1080                     if(ires->argv[0].val.num == nextsrch)
1081                     {
1082                         if(cursrch != -1)
1083                             dc_queuecmd(NULL, NULL, L"cansrch", L"%i", cursrch, NULL);
1084                         cursrch = nextsrch;
1085                         nextsrch = -1;
1086                         gtk_widget_set_sensitive(main_realsrch, TRUE);
1087                         gtk_widget_set_sensitive(main_simplesrch, TRUE);
1088                         gtk_widget_set_sensitive(main_srchbtn, TRUE);
1089                         gtk_widget_set_sensitive(main_srchcanbtn, FALSE);
1090                         srchstatupdate();
1091                         gtk_entry_set_text(GTK_ENTRY(main_realsrch), "");
1092                         gtk_entry_set_text(GTK_ENTRY(main_simplesrch), "");
1093                         gtk_tree_store_clear(srchmodel);
1094                         freesrchsizes();
1095                     }
1096                     dc_freeires(ires);
1097                 }
1098                 break;
1099             case 622:
1100                 if((ires = dc_interpret(resp)) != NULL)
1101                 {
1102                     if(ires->argv[0].val.num == cursrch)
1103                     {
1104                         for(i = 0; i < numsizes; i++)
1105                         {
1106                             if(srchsizes[i].size == ires->argv[4].val.num)
1107                                 break;
1108                         }
1109                         if(i == numsizes)
1110                         {
1111                             srchsizes = srealloc(srchsizes, sizeof(*srchsizes) * ++numsizes);
1112                             srchsizes[i].size = ires->argv[4].val.num;
1113                             srchsizes[i].num = 1;
1114                             srchsizes[i].slots = ires->argv[5].val.num;
1115                             srchsizes[i].resptime = ires->argv[7].val.flnum;
1116                             ss = finddiscsize();
1117                             ss->slots += ires->argv[5].val.num;
1118                             if((ss->resptime == 0.0) || (ss->resptime > ires->argv[7].val.flnum))
1119                                 ss->resptime = ires->argv[7].val.flnum;
1120                             piter = *ref2iter(ss->ref);
1121                             gtk_tree_store_set(srchmodel, &piter, 5, ss->slots, 6, ss->resptime, -1);
1122                             gtk_tree_store_append(srchmodel, &titer, &piter);
1123                             srchsizes[i].ref = iter2ref(&titer);
1124                         } else if(srchsizes[i].num == 1) {
1125                             char *filename, *peername, *fnetname, *hash;
1126                             int slots, speed;
1127                             double resptime;
1128                             
1129                             gtk_tree_model_get(GTK_TREE_MODEL(srchmodel), ref2iter(srchsizes[i].ref), 0, &fnetname, 1, &peername, 3, &filename, 5, &slots, 6, &resptime, 8, &speed, 9, &hash, -1);
1130                             gtk_tree_store_remove(srchmodel, ref2iter(srchsizes[i].ref));
1131                             gtk_tree_row_reference_free(srchsizes[i].ref);
1132                             ss = finddiscsize();
1133                             ss->slots -= slots;
1134                             gtk_tree_store_set(srchmodel, ref2iter(ss->ref), 5, ss->slots, -1);
1135                             gtk_tree_store_append(srchmodel, &piter, NULL);
1136                             srchsizes[i].slots = ires->argv[5].val.num + slots;
1137                             srchsizes[i].resptime = (ires->argv[7].val.flnum < resptime)?ires->argv[7].val.flnum:resptime;
1138                             srchsizes[i].num = 2;
1139                             srchsizes[i].ref = iter2ref(&piter);
1140                             gtk_tree_store_set(srchmodel, &piter, 4, srchsizes[i].size, 5, srchsizes[i].slots, 6, srchsizes[i].resptime, 7, 2, -1);
1141                             if((buf = icwcstombs(ires->argv[1].val.str, "UTF-8")) != NULL)
1142                             {
1143                                 p = buf;
1144                                 /* XXX: Too NMDC-specific! */
1145                                 if(strrchr(p, '\\') != NULL)
1146                                     p = strrchr(p, '\\') + 1;
1147                                 gtk_tree_store_set(srchmodel, &piter, 3, p, -1);
1148                                 free(buf);
1149                             }
1150                             gtk_tree_store_append(srchmodel, &titer, &piter);
1151                             gtk_tree_store_set(srchmodel, &titer, 0, fnetname, 1, peername, 2, peername, 3, filename, 4, srchsizes[i].size, 5, slots, 6, resptime, 8, speed, 9, hash, -1);
1152                             g_free(filename); g_free(peername); g_free(fnetname); g_free(hash);
1153                             gtk_tree_store_append(srchmodel, &titer, &piter);
1154                         } else {
1155                             srchsizes[i].num++;
1156                             srchsizes[i].slots += ires->argv[5].val.num;
1157                             if(ires->argv[7].val.flnum < srchsizes[i].resptime)
1158                                 srchsizes[i].resptime = ires->argv[7].val.flnum;
1159                             piter = *ref2iter(srchsizes[i].ref);
1160                             gtk_tree_store_set(srchmodel, &piter, 5, srchsizes[i].slots, 6, srchsizes[i].resptime, 7, srchsizes[i].num, -1);
1161                             gtk_tree_store_append(srchmodel, &titer, &piter);
1162                         }
1163                         if((buf = icwcstombs(ires->argv[1].val.str, "UTF-8")) != NULL)
1164                         {
1165                             gtk_tree_store_set(srchmodel, &titer, 3, buf, -1);
1166                             free(buf);
1167                         }
1168                         if((buf = icwcstombs(ires->argv[2].val.str, "UTF-8")) != NULL)
1169                         {
1170                             gtk_tree_store_set(srchmodel, &titer, 0, buf, -1);
1171                             free(buf);
1172                         }
1173                         if((buf = icwcstombs(ires->argv[3].val.str, "UTF-8")) != NULL)
1174                         {
1175                             gtk_tree_store_set(srchmodel, &titer, 1, buf, -1);
1176                             gtk_tree_store_set(srchmodel, &titer, 2, buf, -1);
1177                             free(buf);
1178                         }
1179                         if((buf = icwcstombs(ires->argv[8].val.str, "UTF-8")) != NULL)
1180                         {
1181                             gtk_tree_store_set(srchmodel, &titer, 9, buf, -1);
1182                             free(buf);
1183                         }
1184                         gtk_tree_store_set(srchmodel, &titer, 4, ires->argv[4].val.num, 5, ires->argv[5].val.num, 6, ires->argv[7].val.flnum, 8, -1, -1);
1185                     }
1186                     dc_freeires(ires);
1187                 }
1188                 break;
1189             default:
1190                 break;
1191             }
1192         } else if(!wcscmp(resp->cmdname, L"filtercmd")) {
1193             if((ksquerytag >= 0) && (ksquerytag == resp->tag))
1194             {
1195                 for(i = 0; i < resp->numlines; i++)
1196                 {
1197                     assert((ks = findksentbyseq(i)) != NULL);
1198                     ks->speed = wcstol(resp->rlines[i].argv[1], NULL, 10);
1199                     ks->seq = -1;
1200                     ks->fetched = time(NULL);
1201                 }
1202                 ksquerytag = -1;
1203                 ksupdatecb(NULL);
1204             } else if((lsrestag >= 0) && (lsrestag == resp->tag)) {
1205                 for(i = 0; i < resp->numlines; i++)
1206                 {
1207                     if(!wcsncmp(resp->rlines[i].argv[1], L"id:", 3))
1208                     {
1209                         gtk_list_store_append(reslist, &titer);
1210                         gtk_list_store_set(reslist, &titer, 0, icswcstombs(resp->rlines[i].argv[1] + 3, "UTF-8", NULL), -1);
1211                     } else if(!wcsncmp(resp->rlines[i].argv[1], L"size:", 5)) {
1212                         gtk_list_store_set(reslist, &titer, 1, wcstol(resp->rlines[i].argv[1] + 5, NULL, 10), -1);
1213                     } else if(!wcsncmp(resp->rlines[i].argv[1], L"prog:", 5)) {
1214                         gtk_list_store_set(reslist, &titer, 2, wcstol(resp->rlines[i].argv[1] + 5, NULL, 10), -1);
1215                     } else if(!wcsncmp(resp->rlines[i].argv[1], L"name:", 5)) {
1216                         gtk_list_store_set(reslist, &titer, 3, icswcstombs(resp->rlines[i].argv[1] + 5, "UTF-8", NULL), -1);
1217                     } else if(!wcsncmp(resp->rlines[i].argv[1], L"lock:", 5)) {
1218                         if(!wcscmp(resp->rlines[i].argv[1] + 5, L"yes"))
1219                             gtk_list_store_set(reslist, &titer, 4, TRUE, -1);
1220                         else
1221                             gtk_list_store_set(reslist, &titer, 4, FALSE, -1);
1222                     } else if(!wcsncmp(resp->rlines[i].argv[1], L"hash:", 5)) {
1223                         gtk_list_store_set(reslist, &titer, 5, icswcstombs(resp->rlines[i].argv[1] + 5, "UTF-8", NULL), -1);
1224                     }
1225                 }
1226                 lsrestag = -1;
1227                 gtk_widget_set_sensitive(reslist_reload, TRUE);
1228             }
1229         }
1230         dc_freeresp(resp);
1231     }
1232     updatewrite();
1233 }
1234
1235 void dcfdcallback(gpointer data, gint source, GdkInputCondition condition)
1236 {
1237     int errnobak;
1238     
1239     if(((condition & GDK_INPUT_READ) && dc_handleread()) || ((condition & GDK_INPUT_WRITE) && dc_handlewrite()))
1240     {
1241         errnobak = errno;
1242         dcdisconnected();
1243         if(errnobak == 0)
1244         {
1245             msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server has closed the connection"));
1246         } else {
1247             msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The connection to the server failed:\n\n%s"), strerror(errnobak));
1248         }
1249         return;
1250     }
1251     handleresps();
1252 }
1253
1254 void cb_main_dconnmenu_activate(GtkWidget *widget, gpointer data)
1255 {
1256     if(dcfd < 0)
1257         return;
1258     dc_disconnect();
1259     dcdisconnected();
1260 }
1261
1262 void cb_main_prefmenu_activate(GtkWidget *widget, gpointer data)
1263 {
1264     GtkWidget *dialog, *swnd;
1265     int resp;
1266     
1267     dialog = gtk_dialog_new_with_buttons(_("Preferences"), GTK_WINDOW(main_wnd), GTK_DIALOG_MODAL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1268     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), swnd = create_pref_wnd(), TRUE, TRUE, 0);
1269     gtk_entry_set_text(GTK_ENTRY(pref_pubhuburl), pubhubaddr);
1270     gtk_entry_set_text(GTK_ENTRY(pref_connectas), connectas);
1271     gtk_entry_set_text(GTK_ENTRY(pref_dcserver), dcserver);
1272     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pref_autoconn), autoconn);
1273     gtk_widget_show(swnd);
1274     resp = gtk_dialog_run(GTK_DIALOG(dialog));
1275     if(resp == GTK_RESPONSE_ACCEPT)
1276     {
1277         free(pubhubaddr);
1278         pubhubaddr = sstrdup(gtk_entry_get_text(GTK_ENTRY(pref_pubhuburl)));
1279         free(connectas);
1280         connectas = sstrdup(gtk_entry_get_text(GTK_ENTRY(pref_connectas)));
1281         free(dcserver);
1282         dcserver = sstrdup(gtk_entry_get_text(GTK_ENTRY(pref_dcserver)));
1283         autoconn = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_autoconn));
1284     }
1285     gtk_widget_destroy(dialog);
1286 }
1287
1288 void cb_main_lsres_activate(GtkWidget *widget, gpointer data)
1289 {
1290     gtk_list_store_clear(reslist);
1291     gtk_widget_set_sensitive(reslist_delete, FALSE);
1292     gtk_widget_set_sensitive(reslist_search, FALSE);
1293     gtk_widget_show(reslist_wnd);
1294     if(lsrestag == -1)
1295     {
1296         lsrestag = dc_queuecmd(NULL, NULL, L"filtercmd", L"lsres", NULL);
1297         gtk_widget_set_sensitive(reslist_reload, FALSE);
1298     }
1299 }
1300
1301 void dcconnect(char *host)
1302 {
1303     dcfd = dc_connect(host);
1304     if(dcfd < 0)
1305     {
1306         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect:\n\n%s"), strerror(errno));
1307         return;
1308     }
1309     gdkread = gdk_input_add(dcfd, GDK_INPUT_READ, dcfdcallback, NULL);
1310     updatewrite();
1311     gtk_widget_set_sensitive(main_connmenu, FALSE);
1312     gtk_widget_set_sensitive(main_dconnmenu, TRUE);
1313     updatesbar(_("Connecting..."));
1314 }
1315
1316 void cb_main_connmenu_activate(GtkWidget *widget, gpointer data)
1317 {
1318     char *buf;
1319     
1320     if(dcfd >= 0)
1321         return;
1322     if((buf = inputbox(_("Connect"), _("Server address:"), dcserver, 1)) == NULL)
1323         return;
1324     dcconnect(buf);
1325     free(buf);
1326 }
1327
1328 void cb_main_sdmenu_activate(GtkWidget *widget, gpointer data)
1329 {
1330     int tag;
1331     struct dc_response *resp;
1332
1333     if(dcfd < 0)
1334     {
1335         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
1336         return;
1337     }
1338     tag = dc_queuecmd(NULL, NULL, L"shutdown", NULL);
1339     if((resp = dc_gettaggedrespsync(tag)) != NULL)
1340     {
1341         if(resp->code == 502)
1342             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
1343         dc_freeresp(resp);
1344     }
1345     handleresps();
1346 }
1347
1348 void cb_main_fnaddr_activate(GtkWidget *widget, gpointer data)
1349 {
1350     int tag;
1351     struct dc_response *resp;
1352     wchar_t **toks;
1353     
1354     if(dcfd < 0)
1355     {
1356         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
1357         return;
1358     }
1359     toks = dc_lexsexpr(icsmbstowcs((char *)gtk_entry_get_text(GTK_ENTRY(main_fnaddr)), "UTF-8", NULL));
1360     if(*toks == NULL)
1361     {
1362         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Illegal address entered"));
1363         return;
1364     }
1365     if(wcschr(toks[0], L':') == NULL)
1366     {
1367         toks[0] = srealloc(toks[0], (wcslen(toks[0]) + 5) * sizeof(wchar_t));
1368         wcscat(toks[0], L":411");
1369     }
1370     tag = dc_queuecmd(NULL, NULL, L"cnct", L"dc", L"%a", toks, NULL);
1371     dc_freewcsarr(toks);
1372     if((resp = dc_gettaggedrespsync(tag)) != NULL)
1373     {
1374         if(resp->code == 502)
1375             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
1376         if(resp->code == 509)
1377             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("The server could not parse that address"));
1378         if(resp->code == 515)
1379             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("There are too many hubs connected"));
1380         dc_freeresp(resp);
1381     }
1382     gtk_entry_set_text(GTK_ENTRY(main_fnaddr), "");
1383     handleresps();
1384 }
1385
1386 void setpubhubmodel(GtkTreeModel *model, int sortcol, int numcols, int *cols, char **names)
1387 {
1388     GtkTreeViewColumn *col;
1389     GtkCellRenderer *rnd;
1390     GtkTreeModel *sortmodel;
1391     int i;
1392     
1393     while((col = gtk_tree_view_get_column(GTK_TREE_VIEW(main_phublist), 0)) != NULL)
1394         gtk_tree_view_remove_column(GTK_TREE_VIEW(main_phublist), col);
1395     for(i = 0; i < numcols; i++) {
1396         if(gtk_tree_model_get_column_type(model, cols[i]) == G_TYPE_INT64)
1397         {
1398             col = gtk_tree_view_column_new();
1399             gtk_tree_view_column_set_title(col, names[i]);
1400             rnd = gtk_cell_renderer_text_new();
1401             gtk_tree_view_column_pack_start(col, rnd, TRUE);
1402             gtk_tree_view_column_set_cell_data_func(col, rnd, transnicebytefunc2, GINT_TO_POINTER(cols[i]), NULL);
1403         } else {
1404             col = gtk_tree_view_column_new_with_attributes(names[i], gtk_cell_renderer_text_new(), "text", cols[i], NULL);
1405         }
1406         gtk_tree_view_column_set_sort_column_id(col, cols[i]);
1407         gtk_tree_view_column_set_resizable(col, TRUE);
1408         gtk_tree_view_append_column(GTK_TREE_VIEW(main_phublist), col);
1409     }
1410     sortmodel = gtk_tree_model_sort_new_with_model(model);
1411     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(sortmodel), sortcol, GTK_SORT_DESCENDING);
1412     gtk_tree_view_set_model(GTK_TREE_VIEW(main_phublist), sortmodel);
1413     g_object_unref(sortmodel);
1414 }
1415
1416 void cb_main_pubhubfilter_activate(GtkWidget *widget, gpointer data)
1417 {
1418     int err;
1419     const char *buf;
1420     char errbuf[1024];
1421     regex_t *filter;
1422     
1423     buf = gtk_entry_get_text(GTK_ENTRY(main_pubhubfilter));
1424     if(*buf)
1425     {
1426         filter = smalloc(sizeof(*filter));
1427         if((err = regcomp(filter, buf, REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0)
1428         {
1429             regerror(err, filter, errbuf, sizeof(errbuf));
1430             msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not compile regex: %s", errbuf);
1431             regfree(filter);
1432             free(filter);
1433             return;
1434         }
1435     } else {
1436         filter = NULL;
1437     }
1438     fetchhublist(pubhubaddr, filter);
1439 }
1440
1441 void cb_main_pubhubabort_clicked(GtkWidget *widget, gpointer data)
1442 {
1443     aborthublist();
1444 }
1445
1446 void cb_main_dcnctbtn_clicked(GtkWidget *widget, gpointer data)
1447 {
1448     GtkTreeIter iter;
1449     int tag, id;
1450     struct dc_response *resp;
1451     
1452     if(dcfd < 0)
1453     {
1454         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
1455         return;
1456     }
1457     if(!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(main_fnetnodes)), NULL, &iter))
1458     {
1459         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("No hub selected"));
1460         return;
1461     }
1462     gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1);
1463     tag = dc_queuecmd(NULL, NULL, L"dcnct", L"%i", id, NULL);
1464     if((resp = dc_gettaggedrespsync(tag)) != NULL)
1465     {
1466         if(resp->code == 502)
1467             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
1468         dc_freeresp(resp);
1469     }
1470     handleresps();
1471 }
1472
1473 void cb_main_phublist_cchange(GtkWidget *widget, gpointer data)
1474 {
1475     GtkTreeIter iter;
1476     GtkTreeModel *model;
1477     char *addr;
1478     
1479     if(!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(main_phublist)), &model, &iter))
1480         return;
1481     gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &addr, -1);
1482     gtk_entry_set_text(GTK_ENTRY(main_fnaddr), addr);
1483     g_free(addr);
1484 }
1485
1486 void cb_main_phublist_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data)
1487 {
1488     int tag;
1489     struct dc_response *resp;
1490     GtkTreeIter iter;
1491     GtkTreeModel *model;
1492     char *buf;
1493     
1494     if(dcfd < 0)
1495     {
1496         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
1497         return;
1498     }
1499     model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1500     if(!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
1501         return;
1502     gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &buf, -1);
1503     if(strchr(buf, ':') == NULL)
1504     {
1505         buf = g_realloc(buf, strlen(buf) + 5);
1506         strcat(buf, ":411");
1507     }
1508     tag = dc_queuecmd(NULL, NULL, L"cnct", L"dc", L"%s", buf, NULL);
1509     g_free(buf);
1510     gtk_entry_set_text(GTK_ENTRY(main_fnaddr), "");
1511     if((resp = dc_gettaggedrespsync(tag)) != NULL)
1512     {
1513         if(resp->code == 502)
1514             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
1515         if(resp->code == 509)
1516             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("The server could not parse that address"));
1517         if(resp->code == 515)
1518             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("There are too many hubs connected"));
1519         dc_freeresp(resp);
1520     }
1521     handleresps();
1522 }
1523
1524 void cb_main_chatnodes_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer uudata)
1525 {
1526     GtkTreeIter iter;
1527     int id;
1528     struct dc_fnetnode *fn;
1529     struct fndata *data;
1530     
1531     if(!gtk_tree_model_get_iter(GTK_TREE_MODEL(fnmodel), &iter, path))
1532         return;
1533     gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1);
1534     if((fn = dc_findfnetnode(id)) == NULL)
1535         return;
1536     data = fn->udata;
1537     curchat = id;
1538     if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(fnmodel), &iter))
1539     {
1540         do
1541         {
1542             gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1);
1543             if(id == curchat)
1544                 gtk_list_store_set(fnmodel, &iter, 5, "gtk-apply", -1);
1545             else
1546                 gtk_list_store_set(fnmodel, &iter, 5, NULL, -1);
1547         } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(fnmodel), &iter));
1548     }
1549     gtk_text_view_set_buffer(GTK_TEXT_VIEW(main_chatview), GTK_TEXT_BUFFER(data->textbuf));
1550 }
1551
1552 void cb_main_chatstr_activate(GtkWidget *widget, gpointer data)
1553 {
1554     int tag;
1555     const char *buf;
1556     struct dc_response *resp;
1557     
1558     if(dcfd < 0)
1559     {
1560         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
1561         return;
1562     }
1563     if(curchat < 0)
1564     {
1565         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("No hub selected"));
1566         return;
1567     }
1568     buf = gtk_entry_get_text(GTK_ENTRY(main_chatstr));
1569     tag = dc_queuecmd(NULL, NULL, L"sendchat", L"%i", curchat, L"1", L"", L"%s", buf, NULL);
1570     if((resp = dc_gettaggedrespsync(tag)) != NULL)
1571     {
1572         if(resp->code == 502)
1573             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
1574         else if(resp->code == 504)
1575             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("This hub could not support all the types of characters in your chat message"));
1576         else if(resp->code == 513)
1577             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("This hub does not support chatting"));
1578         else if(resp->code != 200)
1579             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to chat (%i)"), resp->code);
1580         dc_freeresp(resp);
1581     }
1582     gtk_entry_set_text(GTK_ENTRY(main_chatstr), "");
1583     handleresps();
1584 }
1585
1586 void updatesrchfld(const char *simple)
1587 {
1588     char *buf, *s;
1589     char *p, *p2;
1590     size_t bufsize, bufdata;
1591     
1592     s = sstrdup(simple);
1593     buf = NULL;
1594     bufsize = bufdata = 0;
1595     p = s;
1596     do
1597     {
1598         p2 = strchr(p, ' ');
1599         if(p2 != NULL)
1600             *(p2++) = 0;
1601         if(*p)
1602         {
1603             if(bufdata > 0)
1604                 bufcat(buf, " & ", 3);
1605             bufcat(buf, "N~", 2);
1606             for(; *p; p++)
1607             {
1608                 if(strchr("[]()$^.*?+\\|\"!", *p) != NULL)
1609                     addtobuf(buf, '\\');
1610                 addtobuf(buf, *p);
1611             }
1612         }
1613         p = p2;
1614     } while(p2 != NULL);
1615     addtobuf(buf, 0);
1616     gtk_entry_set_text(GTK_ENTRY(main_realsrch), buf);
1617     free(buf);
1618     free(s);
1619 }
1620
1621 void cb_main_simplesrch_changed(GtkWidget *widget, gpointer data)
1622 {
1623     if(srchautoupdate)
1624         return;
1625     srchautoupdate = 1;
1626     updatesrchfld(gtk_entry_get_text(GTK_ENTRY(main_simplesrch)));
1627     srchautoupdate = 0;
1628 }
1629
1630 void cb_main_realsrch_changed(GtkWidget *widget, gpointer data)
1631 {
1632     if(srchautoupdate)
1633         return;
1634     srchautoupdate = 1;
1635     gtk_entry_set_text(GTK_ENTRY(main_simplesrch), "");
1636     srchautoupdate = 0;
1637 }
1638
1639 void cb_main_srchbtn_clicked(GtkWidget *widget, gpointer data)
1640 {
1641     wchar_t **toks;
1642     int tag;
1643     struct dc_response *resp;
1644     struct dc_intresp *ires;
1645     
1646     if(dcfd < 0)
1647     {
1648         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
1649         return;
1650     }
1651     if(nextsrch != -1) /* Impossible case, but oh well... */
1652         return;
1653     toks = dc_lexsexpr(icsmbstowcs((char *)gtk_entry_get_text(GTK_ENTRY(main_realsrch)), "UTF-8", NULL));
1654     if(*toks == NULL)
1655     {
1656         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Please enter a search expression before searching"));
1657         return;
1658     }
1659     tag = dc_queuecmd(NULL, NULL, L"search", L"all", L"%a", toks, NULL);
1660     dc_freewcsarr(toks);
1661     if((resp = dc_gettaggedrespsync(tag)) != NULL)
1662     {
1663         if(resp->code == 501)
1664             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not find any hubs to search on"));
1665         else if(resp->code == 502)
1666             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
1667         else if(resp->code == 509)
1668             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("The server could not parse your search expression"));
1669         else if(resp->code != 200)
1670             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to search (%i)"), resp->code);
1671         if(resp->code == 200)
1672         {
1673             if((ires = dc_interpret(resp)) != NULL)
1674             {
1675                 nextsrch = ires->argv[0].val.num;
1676                 srcheta = time(NULL) + ires->argv[1].val.num;
1677                 dc_freeires(ires);
1678             }
1679             gtk_widget_set_sensitive(main_realsrch, FALSE);
1680             gtk_widget_set_sensitive(main_simplesrch, FALSE);
1681             gtk_widget_set_sensitive(main_srchbtn, FALSE);
1682             gtk_widget_set_sensitive(main_srchcanbtn, TRUE);
1683             srchstatupdate();
1684         }
1685         dc_freeresp(resp);
1686     }
1687     handleresps();
1688 }
1689
1690 void cb_main_srchcanbtn_clicked(GtkWidget *widget, gpointer data)
1691 {
1692     if(nextsrch == -1)
1693         return;
1694     dc_queuecmd(NULL, NULL, L"cansrch", L"%i", nextsrch, NULL);
1695     nextsrch = -1;
1696     gtk_widget_set_sensitive(main_realsrch, TRUE);
1697     gtk_widget_set_sensitive(main_simplesrch, TRUE);
1698     gtk_widget_set_sensitive(main_srchbtn, TRUE);
1699     gtk_widget_set_sensitive(main_srchcanbtn, FALSE);
1700     srchstatupdate();
1701 }
1702
1703 gboolean cb_main_trlist_keypress(GtkWidget *widget, GdkEventKey *event, gpointer data)
1704 {
1705     int id, tag;
1706     GtkTreeSelection *sel;
1707     GtkTreeModel *model;
1708     GtkTreeIter iter;
1709     struct dc_response *resp;
1710     
1711     if((event->type == GDK_KEY_PRESS) && (event->keyval == GDK_Delete))
1712     {
1713         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1714         if(gtk_tree_selection_get_selected(sel, &model, &iter))
1715         {
1716             gtk_tree_model_get(model, &iter, 0, &id, -1);
1717             tag = dc_queuecmd(NULL, NULL, L"cancel", L"%i", id, NULL);
1718             if((resp = dc_gettaggedrespsync(tag)) != NULL)
1719             {
1720                 if(resp->code == 502)
1721                     msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
1722                 else if(resp->code != 200)
1723                     msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to cancel (%i)"), resp->code);
1724                 dc_freeresp(resp);
1725             }
1726             handleresps();
1727         }
1728         return(TRUE);
1729     }
1730     return(FALSE);
1731 }
1732
1733 void cb_main_srchres_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data)
1734 {
1735     int tag;
1736     struct dc_response *resp;
1737     GtkTreeIter iter;
1738     GtkTreeModel *model;
1739     int size, num;
1740     char *tfnet, *tpeerid, *tfilename, *thash, *arg;
1741     wchar_t *fnet, *peerid, *filename, *hash;
1742     
1743     if(dcfd < 0)
1744     {
1745         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
1746         return;
1747     }
1748     model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1749     if(!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
1750         return;
1751     gtk_tree_model_get(model, &iter, 7, &num, -1);
1752     if(num > 0)
1753         return;
1754     gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &tfnet, 1, &tpeerid, 3, &tfilename, 4, &size, 9, &thash, -1);
1755     fnet = icmbstowcs(tfnet, "UTF-8");
1756     peerid = icmbstowcs(tpeerid, "UTF-8");
1757     filename = icmbstowcs(tfilename, "UTF-8");
1758     hash = (thash == NULL)?NULL:icmbstowcs(thash, "UTF-8");
1759     if((fnet == NULL) || (peerid == NULL) || (filename == NULL))
1760     {
1761         if(fnet != NULL)
1762             free(fnet);
1763         if(peerid != NULL)
1764             free(peerid);
1765         if(filename != NULL)
1766             free(filename);
1767         if(hash != NULL)
1768             free(hash);
1769         g_free(tfnet);
1770         g_free(tpeerid);
1771         g_free(tfilename);
1772         if(thash != NULL)
1773             g_free(thash);
1774         return;
1775     }
1776     g_free(tfnet);
1777     g_free(tpeerid);
1778     g_free(tfilename);
1779     arg = (char *)gtk_entry_get_text(GTK_ENTRY(main_dlarg));
1780     if(*arg)
1781         tag = dc_queuecmd(NULL, NULL, L"download", fnet, L"%ls", peerid, L"%ls", filename, L"%i", size, L"hash", L"%ls", (hash == NULL)?L"":hash, L"user", L"%s", arg, NULL);
1782     else
1783         tag = dc_queuecmd(NULL, NULL, L"download", fnet, L"%ls", peerid, L"%ls", filename, L"%i", size, L"hash", L"%ls", (hash == NULL)?L"":hash, NULL);
1784     free(fnet);
1785     free(peerid);
1786     free(filename);
1787     if(hash != NULL)
1788         free(hash);
1789     if((resp = dc_gettaggedrespsync(tag)) != NULL)
1790     {
1791         if(resp->code == 502)
1792             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
1793         if(resp->code != 200)
1794             msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to queue the download (%i)"), resp->code);
1795         dc_freeresp(resp);
1796     }
1797     handleresps();
1798 }
1799
1800 gboolean srchfilterfunc(GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1801 {
1802     int slots;
1803     int filteratall;
1804     
1805     filteratall = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(main_filternoslots));
1806     if(!filteratall)
1807         return(TRUE);
1808     gtk_tree_model_get(model, iter, 5, &slots, -1);
1809     if(slots < 1)
1810         return(FALSE);
1811     return(TRUE);
1812 }
1813
1814 void cb_main_filternoslots_toggled(GtkToggleButton *widget, gpointer data)
1815 {
1816     gtk_tree_model_filter_refilter(srchmodelfilter);
1817 }
1818
1819 void cb_main_srhash_activate(GtkWidget *widget, gpointer data)
1820 {
1821     GtkTreeSelection *sel;
1822     GtkTreeModel *model;
1823     GtkTreeIter iter;
1824     char *hash, *buf;
1825     
1826     if(nextsrch != -1)
1827         return;
1828     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(main_srchres));
1829     if(gtk_tree_selection_get_selected(sel, &model, &iter))
1830     {
1831         gtk_tree_model_get(model, &iter, 9, &hash, -1);
1832         buf = sprintf2("H=%s", hash);
1833         gtk_entry_set_text(GTK_ENTRY(main_realsrch), buf);
1834         g_free(hash);
1835         free(buf);
1836         cb_main_srchbtn_clicked(widget, NULL);
1837     } else {
1838         return;
1839     }
1840 }
1841
1842 void cb_main_srcopy_activate(GtkWidget *widget, gpointer data)
1843 {
1844     GtkClipboard *cb;
1845     GtkTreeSelection *sel;
1846     GtkTreeModel *model;
1847     GtkTreeIter iter;
1848     char *hash;
1849     
1850     if(nextsrch != -1)
1851         return;
1852     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(main_srchres));
1853     if(!gtk_tree_selection_get_selected(sel, &model, &iter))
1854         return;
1855     gtk_tree_model_get(model, &iter, 9, &hash, -1);
1856     cb = gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
1857     gtk_clipboard_set_text(cb, hash, -1);
1858     g_free(hash);
1859 }
1860
1861 void cb_main_trhash_activate(GtkWidget *widget, gpointer data)
1862 {
1863     GtkTreeSelection *sel;
1864     GtkTreeModel *model;
1865     GtkTreeIter iter;
1866     char *hash, *buf;
1867     
1868     if(nextsrch != -1)
1869         return;
1870     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(main_downloads));
1871     if(gtk_tree_selection_get_selected(sel, &model, &iter))
1872     {
1873         gtk_tree_model_get(model, &iter, 12, &hash, -1);
1874         buf = sprintf2("H=%s", hash);
1875         gtk_entry_set_text(GTK_ENTRY(main_realsrch), buf);
1876         g_free(hash);
1877         free(buf);
1878         cb_main_srchbtn_clicked(widget, NULL);
1879     } else {
1880         return;
1881     }
1882 }
1883
1884 void cb_main_trcopy_activate(GtkWidget *widget, gpointer data)
1885 {
1886     GtkClipboard *cb;
1887     GtkTreeSelection *sel;
1888     GtkTreeModel *model;
1889     GtkTreeIter iter;
1890     char *hash;
1891     
1892     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(main_downloads));
1893     if(!gtk_tree_selection_get_selected(sel, &model, &iter))
1894         return;
1895     gtk_tree_model_get(model, &iter, 12, &hash, -1);
1896     cb = gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
1897     gtk_clipboard_set_text(cb, hash, -1);
1898     g_free(hash);
1899 }
1900
1901 void cb_main_trreset_activate(GtkWidget *widget, gpointer data)
1902 {
1903     GtkTreeSelection *sel;
1904     GtkTreeModel *model;
1905     GtkTreeIter iter;
1906     int id, tag;
1907     struct dc_response *resp;
1908     
1909     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(main_downloads));
1910     if(gtk_tree_selection_get_selected(sel, &model, &iter))
1911     {
1912         gtk_tree_model_get(model, &iter, 0, &id, -1);
1913         tag = dc_queuecmd(NULL, NULL, L"reset", L"%i", id, NULL);
1914         if((resp = dc_gettaggedrespsync(tag)) != NULL)
1915         {
1916             if(resp->code == 502)
1917                 msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
1918             else if(resp->code != 200)
1919                 msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to reset (%i)"), resp->code);
1920             dc_freeresp(resp);
1921         }
1922         handleresps();
1923     } else {
1924         return;
1925     }
1926 }
1927
1928 void cb_main_trcancel_activate(GtkWidget *widget, gpointer data)
1929 {
1930     GtkTreeSelection *sel;
1931     GtkTreeModel *model;
1932     GtkTreeIter iter;
1933     int id, tag;
1934     struct dc_response *resp;
1935     
1936     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(main_downloads));
1937     if(gtk_tree_selection_get_selected(sel, &model, &iter))
1938     {
1939         gtk_tree_model_get(model, &iter, 0, &id, -1);
1940         tag = dc_queuecmd(NULL, NULL, L"cancel", L"%i", id, NULL);
1941         if((resp = dc_gettaggedrespsync(tag)) != NULL)
1942         {
1943             if(resp->code == 502)
1944                 msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
1945             else if(resp->code != 200)
1946                 msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to cancel (%i)"), resp->code);
1947             dc_freeresp(resp);
1948         }
1949         handleresps();
1950     } else {
1951         return;
1952     }
1953 }
1954
1955 /* XXX: This is quite a hack, since the calling convention is
1956  * different for the popup-menu sig and the button-press-event sig. It
1957  * most certainly works, but I don't know how portable it is. */
1958 gboolean cb_main_srpopup(GtkWidget *widget, GdkEventButton *event, gpointer data)
1959 {
1960     GtkTreeSelection *sel;
1961     GtkTreeModel *model;
1962     GtkTreeIter iter;
1963     char *hash;
1964     
1965     if((event != NULL) && (event->button != 3))
1966         return(FALSE);
1967     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1968     if(gtk_tree_selection_get_selected(sel, &model, &iter))
1969     {
1970         gtk_tree_model_get(model, &iter, 9, &hash, -1);
1971         if((hash == NULL) || (*hash == 0))
1972         {
1973             gtk_widget_set_sensitive(main_srhash, FALSE);
1974             gtk_widget_set_sensitive(main_srcopy, FALSE);
1975         } else {
1976             if(nextsrch == -1)
1977                 gtk_widget_set_sensitive(main_srhash, TRUE);
1978             else
1979                 gtk_widget_set_sensitive(main_srhash, FALSE);
1980             gtk_widget_set_sensitive(main_srcopy, TRUE);
1981         }
1982         g_free(hash);
1983     } else {
1984         return(FALSE);
1985     }
1986     if(event == NULL)
1987         gtk_menu_popup(GTK_MENU(main_srpopup), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
1988     else
1989         gtk_menu_popup(GTK_MENU(main_srpopup), NULL, NULL, NULL, NULL, event->button, event->time);
1990     return(FALSE);
1991 }
1992
1993 /* The above hack note goes for this one too. */
1994 gboolean cb_main_trpopup(GtkWidget *widget, GdkEventButton *event, gpointer data)
1995 {
1996     GtkTreeSelection *sel;
1997     GtkTreeModel *model;
1998     GtkTreeIter iter;
1999     char *hash;
2000     
2001     if((event != NULL) && (event->button != 3))
2002         return(FALSE);
2003     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
2004     if(gtk_tree_selection_get_selected(sel, &model, &iter))
2005     {
2006         gtk_tree_model_get(model, &iter, 12, &hash, -1);
2007         if((hash == NULL) || (*hash == 0))
2008         {
2009             gtk_widget_set_sensitive(main_trhash, FALSE);
2010             gtk_widget_set_sensitive(main_trcopy, FALSE);
2011         } else {
2012             if(nextsrch == -1)
2013                 gtk_widget_set_sensitive(main_trhash, TRUE);
2014             else
2015                 gtk_widget_set_sensitive(main_trhash, FALSE);
2016             gtk_widget_set_sensitive(main_trcopy, TRUE);
2017         }
2018         g_free(hash);
2019     } else {
2020         return(FALSE);
2021     }
2022     if(event == NULL)
2023         gtk_menu_popup(GTK_MENU(main_trpopup), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
2024     else
2025         gtk_menu_popup(GTK_MENU(main_trpopup), NULL, NULL, NULL, NULL, event->button, event->time);
2026     return(FALSE);
2027 }
2028
2029 void cb_reslist_reload_clicked(GtkWidget *widget, gpointer data)
2030 {
2031     if(lsrestag != -1)
2032         return;
2033     gtk_widget_set_sensitive(reslist_delete, FALSE);
2034     gtk_widget_set_sensitive(reslist_search, FALSE);
2035     gtk_list_store_clear(reslist);
2036     lsrestag = dc_queuecmd(NULL, NULL, L"filtercmd", L"lsres", NULL);
2037     gtk_widget_set_sensitive(reslist_reload, FALSE);
2038 }
2039
2040 int rmres(char *id)
2041 {
2042     int tag, ret;
2043     struct dc_response *resp;
2044     
2045     ret = -1;
2046     tag = dc_queuecmd(NULL, NULL, L"filtercmd", L"rmres", L"%s", id, NULL);
2047     if((resp = dc_gettaggedrespsync(tag)) != NULL)
2048     {
2049         if(resp->numlines > 0)
2050         {
2051             if(!wcscmp(resp->rlines[0].argv[1], L"ok"))
2052                 ret = 0;
2053             else if(!wcsncmp(resp->rlines[0].argv[1], L"err:", 4))
2054                 msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred (%ls)"), resp->rlines[0].argv[1] + 4);
2055         }
2056         dc_freeresp(resp);
2057     }
2058     handleresps();
2059     return(ret);
2060 }
2061
2062 void cb_reslist_delete_clicked(GtkWidget *widget, gpointer data)
2063 {
2064     GtkTreeIter iter;
2065     GtkTreeModel *model;
2066     char *id;
2067     gboolean locked;
2068     
2069     if(nextsrch != -1)
2070         return;
2071     if(!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(reslist_list)), &model, &iter))
2072         return;
2073     gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &id, 4, &locked, -1);
2074     if(locked)
2075     {
2076         g_free(id);
2077         return;
2078     }
2079     if(!rmres(id))
2080         gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
2081     g_free(id);
2082 }
2083
2084 void cb_reslist_search_clicked(GtkWidget *widget, gpointer data)
2085 {
2086     GtkTreeIter iter;
2087     GtkTreeModel *model;
2088     char *hash, *buf;
2089     
2090     if(nextsrch != -1)
2091         return;
2092     if(!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(reslist_list)), &model, &iter))
2093         return;
2094     gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 5, &hash, -1);
2095     buf = sprintf2("H=%s", hash);
2096     gtk_entry_set_text(GTK_ENTRY(main_realsrch), buf);
2097     free(buf);
2098     g_free(hash);
2099     cb_main_srchbtn_clicked(widget, NULL);
2100 }
2101
2102 void cb_reslist_list_cchange(GtkWidget *widget, gpointer data)
2103 {
2104     GtkTreeIter iter;
2105     GtkTreeModel *model;
2106     gboolean locked;
2107     char *hash;
2108     
2109     if(!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(reslist_list)), &model, &iter))
2110     {
2111         gtk_widget_set_sensitive(reslist_delete, FALSE);
2112         gtk_widget_set_sensitive(reslist_search, FALSE);
2113         return;
2114     }
2115     gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 4, &locked, 5, &hash, -1);
2116     gtk_widget_set_sensitive(reslist_delete, !locked);
2117     gtk_widget_set_sensitive(reslist_search, hash && *hash);
2118     g_free(hash);
2119 }
2120
2121 void cb_reslist_list_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data)
2122 {
2123     GtkTreeIter iter;
2124     GtkTreeModel *model;
2125     char *hash, *buf;
2126
2127     model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
2128     if(!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
2129         return;
2130     if(nextsrch != -1)
2131         return;
2132     gtk_tree_model_get(model, &iter, 5, &hash, -1);
2133     buf = sprintf2("H=%s", hash);
2134     gtk_entry_set_text(GTK_ENTRY(main_realsrch), buf);
2135     free(buf);
2136     g_free(hash);
2137     cb_main_srchbtn_clicked(widget, NULL);
2138 }
2139
2140 gboolean cb_reslist_list_keypress(GtkWidget *widget, GdkEventKey *event, gpointer data)
2141 {
2142     GtkTreeSelection *sel;
2143     GtkTreeModel *model;
2144     GtkTreeIter iter;
2145     char *id;
2146     gboolean locked;
2147     
2148     if((event->type == GDK_KEY_PRESS) && (event->keyval == GDK_Delete))
2149     {
2150         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
2151         if(gtk_tree_selection_get_selected(sel, &model, &iter))
2152         {
2153             gtk_tree_model_get(model, &iter, 0, &id, 4, &locked, -1);
2154             if(!locked)
2155             {
2156                 if(!rmres(id))
2157                     gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
2158             }
2159             g_free(id);
2160         }
2161         return(TRUE);
2162     }
2163     return(FALSE);
2164 }
2165
2166 void srchstatupdate(void)
2167 {
2168     char buf[1024];
2169     
2170     if(nextsrch == -1)
2171     {
2172         snprintf(buf, 1024, _("Ready to search"));
2173     } else {
2174         snprintf(buf, 1024, _("Search scheduled and will be submitted in %i seconds"), (int)(srcheta - time(NULL)));
2175     }
2176     if(strcmp(gtk_label_get_text(GTK_LABEL(main_srchstatus)), buf))
2177         gtk_label_set_text(GTK_LABEL(main_srchstatus), buf);
2178 }
2179
2180 gint srchstatupdatecb(gpointer data)
2181 {
2182     srchstatupdate();
2183     return(TRUE);
2184 }
2185
2186 void initchattags(void)
2187 {
2188     GtkTextTag *tag;
2189     
2190     chattags = gtk_text_tag_table_new();
2191     tag = gtk_text_tag_new("sender");
2192     g_object_set(tag, "foreground", "blue", NULL);
2193     gtk_text_tag_table_add(chattags, tag);
2194 }
2195
2196 #include "../dolda-icon.xpm"
2197
2198 int main(int argc, char **argv)
2199 {
2200     int c, connlocal;
2201     GtkWidget *wnd;
2202     PangoFontDescription *monospacefont;
2203     GtkTreeModel *sortmodel;
2204     struct passwd *pwent;
2205     
2206     setlocale(LC_ALL, "");
2207     bindtextdomain(PACKAGE, LOCALEDIR);
2208     textdomain(PACKAGE);
2209     gtk_init(&argc, &argv);
2210     connlocal = 0;
2211     while((c = getopt(argc, argv, "lhV")) != -1) {
2212         switch(c) {
2213         case 'l':
2214             connlocal = 1;
2215             break;
2216         case 'h':
2217             printf("usage: dolcon [-hlV]\n");
2218             printf("\t-h\tDisplay this help message\n");
2219             printf("\t-l\tConnect to the locally running daemon\n");
2220             printf("\t-V\tDisplay version info and exit\n");
2221             exit(0);
2222         case 'V':
2223             printf("%s", RELEASEINFO);
2224             exit(0);
2225         default:
2226             fprintf(stderr, "usage: dolcon [-hlV]\n");
2227             exit(1);
2228         }
2229     }
2230     dc_init();
2231     signal(SIGCHLD, SIG_IGN);
2232     pubhubaddr = sstrdup("http://www.hublist.org/PublicHubList.xml.bz2");
2233     dcserver = sstrdup("");
2234     if((pwent = getpwuid(getuid())) == NULL)
2235     {
2236         fprintf(stderr, "could not get your passwd data");
2237         exit(1);
2238     }
2239     connectas = sstrdup(pwent->pw_name);
2240     gtk_window_set_default_icon(gdk_pixbuf_new_from_xpm_data((const char **)dolda_icon_xpm));
2241     wnd = create_main_wnd();
2242     create_reslist_wnd();
2243     gtk_window_resize(GTK_WINDOW(reslist_wnd), 600, 400);
2244     initchattags();
2245
2246     fnmodel = gtk_list_store_new(6, G_TYPE_INT, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING);
2247     gtk_tree_view_set_model(GTK_TREE_VIEW(main_fnetnodes), GTK_TREE_MODEL(fnmodel));
2248     gtk_tree_view_set_model(GTK_TREE_VIEW(main_chatnodes), GTK_TREE_MODEL(fnmodel));
2249     
2250     reslist = gtk_list_store_new(6, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING);
2251     gtk_tree_view_set_model(GTK_TREE_VIEW(reslist_list), GTK_TREE_MODEL(reslist));
2252     
2253     dlmodel = gtk_list_store_new(13, G_TYPE_INT, /* id */
2254                                  G_TYPE_INT,     /* dir */
2255                                  G_TYPE_INT,     /* state */
2256                                  G_TYPE_STRING,  /* peerid */
2257                                  G_TYPE_STRING,  /* peernick */
2258                                  G_TYPE_STRING,  /* path */
2259                                  G_TYPE_INT,     /* size */
2260                                  G_TYPE_INT,     /* curpos */
2261                                  G_TYPE_STRING,  /* stock */
2262                                  G_TYPE_FLOAT,   /* percentage */
2263                                  G_TYPE_INT,     /* error */
2264                                  G_TYPE_INT,     /* errortime */
2265                                  G_TYPE_STRING); /* hash */
2266     gtk_tree_view_set_model(GTK_TREE_VIEW(main_downloads), GTK_TREE_MODEL(dlmodel));
2267
2268     ulmodel = gtk_list_store_new(13, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
2269     gtk_tree_view_set_model(GTK_TREE_VIEW(main_uploads), GTK_TREE_MODEL(ulmodel));
2270
2271     srchmodel = gtk_tree_store_new(10, G_TYPE_STRING, /* fnetname */
2272                                    G_TYPE_STRING,     /* peerid */
2273                                    G_TYPE_STRING,     /* peername */
2274                                    G_TYPE_STRING,     /* filename */
2275                                    G_TYPE_INT,        /* size */
2276                                    G_TYPE_INT,        /* slots */
2277                                    G_TYPE_DOUBLE,     /* resptime */
2278                                    G_TYPE_INT,        /* sizenum */
2279                                    G_TYPE_INT,        /* speed */
2280                                    G_TYPE_STRING);    /* hash */
2281     srchmodelfilter = GTK_TREE_MODEL_FILTER(gtk_tree_model_filter_new(GTK_TREE_MODEL(srchmodel), NULL));
2282     gtk_tree_model_filter_set_visible_func(srchmodelfilter, srchfilterfunc, NULL, NULL);
2283     sortmodel = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(srchmodelfilter));
2284     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(sortmodel), 4, GTK_SORT_DESCENDING);
2285     gtk_tree_view_set_model(GTK_TREE_VIEW(main_srchres), GTK_TREE_MODEL(sortmodel));
2286     g_object_unref(sortmodel);
2287
2288     monospacefont = pango_font_description_from_string("Monospace 10");
2289     gtk_widget_modify_font(main_chatview, monospacefont);
2290     pango_font_description_free(monospacefont);
2291     readconfigfile();
2292     updatesbar(_("Disconnected"));
2293     gtk_widget_show(wnd);
2294     if(connlocal)
2295         dcconnect(dc_srv_local);
2296     else if(autoconn)
2297         dcconnect(dcserver);
2298     g_timeout_add(500, srchstatupdatecb, NULL);
2299     g_timeout_add(5000, ksupdatecb, NULL);
2300     g_timeout_add(1000, updatetransfers, NULL);
2301     gtk_main();
2302     return(0);
2303 }
2304
2305 #include "mainwnd.gtk"
2306 #include "inpdialog.gtk"
2307 #include "pref.gtk"
2308 #include "reslist.gtk"