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