Allow connecting to any server in dsh.
[doldaconnect.git] / clients / gui-shell / dsh.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2007 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 <stdlib.h>
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <pwd.h>
26 #include <locale.h>
27 #include <libintl.h>
28 #include <signal.h>
29 #include <sys/wait.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <stdarg.h>
33 #include <doldaconnect/uilib.h>
34 #include <doldaconnect/uimisc.h>
35 #include <doldaconnect/utils.h>
36
37 #ifdef HAVE_CONFIG_H
38 #include <config.h>
39 #endif
40
41 #ifdef HAVE_NOTIFY
42 #include <libnotify/notify.h>
43 #endif
44
45 #define _(text) gettext(text)
46
47 struct trinfo {
48     int ostate;
49     int opos, spos, speed;
50     time_t lastprog;
51     double sprog;
52 };
53
54 void updatewrite(void);
55
56 int remote = 0;
57 char *server;
58 GtkStatusIcon *tray;
59 pid_t dpid = 0, dcpid = 0;
60 int connected = 0;
61 int dstat;
62 int derrfd, derrtag;
63 char *derrbuf = NULL;
64 size_t derrbufsize = 0, derrbufdata = 0;
65 int dcfd, dcfdrtag, dcfdwtag = -1;
66 GdkPixbuf *dcicon;
67 #ifdef HAVE_NOTIFY
68 NotifyNotification *trnote = NULL;
69 #endif
70
71 #include "dsh-start.gtkh"
72 #include "dsh-menu.gtkh"
73
74 int running(char *pf)
75 {
76     FILE *pfs;
77     char buf[1024];
78     int pid;
79     
80     if((pfs = fopen(pf, "r")) == NULL) {
81         perror(pf);
82         return(0);
83     }
84     fgets(buf, sizeof(buf), pfs);
85     fclose(pfs);
86     if((pid = atoi(buf)) == 0)
87         return(0);
88     return(!kill(pid, 0));
89 }
90
91 void derrcb(gpointer data, gint source, GdkInputCondition cond)
92 {
93     int ret = 0;
94     
95     sizebuf2(derrbuf, derrbufdata + 1024, 1);
96     ret = read(derrfd, derrbuf + derrbufdata, derrbufsize - derrbufdata);
97     if(ret <= 0) {
98         if(ret < 0)
99             bprintf(derrbuf, "\ncould not read from daemon: %s\n", strerror(errno));
100         gdk_input_remove(derrtag);
101         close(derrfd);
102         derrfd = -1;
103     } else {
104         derrbufdata += ret;
105     }
106 }
107
108 int msgbox(int type, int buttons, char *format, ...)
109 {
110     GtkWidget *swnd;
111     va_list args;
112     char *buf;
113     int resp;
114     
115     va_start(args, format);
116     buf = vsprintf2(format, args);
117     va_end(args);
118     swnd = gtk_message_dialog_new(NULL, 0, type, buttons, "%s", buf);
119     resp = gtk_dialog_run(GTK_DIALOG(swnd));
120     gtk_widget_destroy(swnd);
121     free(buf);
122     return(resp);
123 }
124
125 void destroytr(struct dc_transfer *tr)
126 {
127     struct trinfo *tri;
128     
129     tri = tr->udata;
130     free(tri);
131 }
132
133 void inittr(struct dc_transfer *tr)
134 {
135     struct trinfo *tri;
136     
137     tr->udata = tri = memset(smalloc(sizeof(*tri)), 0, sizeof(*tri));
138     tr->destroycb = destroytr;
139     tri->ostate = tr->state;
140     tri->spos = tri->opos = tr->curpos;
141     tri->speed = -1;
142     tri->lastprog = time(NULL);
143     tri->sprog = ntime();
144 }
145
146 #ifdef HAVE_NOTIFY
147 void notify(NotifyNotification **n, char *cat, char *title, char *body, ...)
148 {
149     va_list args;
150     char *bbuf;
151     
152     va_start(args, body);
153     bbuf = vsprintf2(body, args);
154     va_end(args);
155     if(*n == NULL) {
156         *n = notify_notification_new_with_status_icon(title, bbuf, NULL, tray);
157         notify_notification_set_icon_from_pixbuf(*n, dcicon);
158     } else {
159         notify_notification_update(*n, title, bbuf, NULL);
160     }
161     notify_notification_show(*n, NULL);
162 }
163 #endif
164
165 /* XXX: Achtung! Too DC-specific! */
166 wchar_t *getfilename(wchar_t *path)
167 {
168     wchar_t *p;
169     
170     if((p = wcsrchr(path, L'\\')) == NULL)
171         return(path);
172     else
173         return(p + 1);
174 }
175
176 char *bytes2si(long long bytes)
177 {
178     int i;
179     double b;
180     char *sd;
181     static char ret[64];
182     
183     b = bytes;
184     for(i = 0; (b >= 1024) && (i < 4); i++)
185         b /= 1024;
186     if(i == 0)
187         sd = "B";
188     else if(i == 1)
189         sd = "kiB";
190     else if(i == 2)
191         sd = "MiB";
192     else if(i == 3)
193         sd = "GiB";
194     else
195         sd = "TiB";
196     snprintf(ret, 64, "%.1f %s", b, sd);
197     return(ret);
198 }
199
200 void updatetooltip(void)
201 {
202     struct dc_transfer *tr;
203     struct trinfo *tri;
204     int t, i, a, st, bc, bt;
205     char *buf;
206     size_t bufsize, bufdata;
207     
208     t = i = a = 0;
209     st = bc = bt = -1;
210     for(tr = dc_transfers; tr != NULL; tr = tr->next) {
211         if(tr->dir != DC_TRNSD_DOWN)
212             continue;
213         tri = tr->udata;
214         t++;
215         if(tr->state == DC_TRNS_WAITING)
216             i++;
217         else if((tr->state == DC_TRNS_HS) || (tr->state == DC_TRNS_MAIN))
218             a++;
219         if((tr->state == DC_TRNS_MAIN)) {
220             if(bt == -1)
221                 bc = bt = 0;
222             bc += tr->curpos;
223             bt += tr->size;
224             if(tri->speed != -1) {
225                 if(st == -1)
226                     st = 0;
227                 st += tri->speed;
228             }
229         }
230     }
231     buf = NULL;
232     bufsize = bufdata = 0;
233     bprintf(buf, "%s: %i", _("Transfers"), t);
234     if(t > 0)
235         bprintf(buf, " (%i/%i)", i, a);
236     if(bt > 0)
237         bprintf(buf, ", %.1f%%", ((double)bc / (double)bt) * 100.0);
238     if(st != -1)
239         bprintf(buf, ", %s/s", bytes2si(st));
240     addtobuf(buf, 0);
241     gtk_status_icon_set_tooltip(tray, buf);
242     free(buf);
243 }
244
245 void trstatechange(struct dc_transfer *tr, int ostate)
246 {
247     struct trinfo *tri;
248     
249     tri = tr->udata;
250     if((ostate == DC_TRNS_MAIN) && (tr->dir == DC_TRNSD_DOWN)) {
251         if(tr->state == DC_TRNS_DONE) {
252 #ifdef HAVE_NOTIFY
253             if(dcpid == 0)
254                 notify(&trnote, "transfer.complete", _("Transfer complete"), _("Finished downloading %ls from %ls"), getfilename(tr->path), tr->peernick);
255 #endif
256         } else {
257 #ifdef HAVE_NOTIFY
258             if(dcpid == 0)
259                 notify(&trnote, "transfer.error", _("Transfer interrupted"), _("The transfer of %ls from %ls was interrupted from the other side"), getfilename(tr->path), tr->peernick);
260 #endif
261         }
262     }
263     if(tr->state == DC_TRNS_MAIN) {
264         tri->speed = -1;
265         tri->spos = tr->curpos;
266         tri->sprog = ntime();
267     }
268 }
269
270 void updatetrinfo(void)
271 {
272     struct dc_transfer *tr;
273     struct trinfo *tri;
274     time_t now;
275     double dnow;
276     
277     now = time(NULL);
278     dnow = ntime();
279     for(tr = dc_transfers; tr != NULL; tr = tr->next) {
280         if(tr->udata == NULL) {
281             inittr(tr);
282         } else {
283             tri = tr->udata;
284             if(tri->ostate != tr->state) {
285                 trstatechange(tr, tri->ostate);
286                 tri->ostate = tr->state;
287             }
288             if(tri->opos != tr->curpos) {
289                 tri->opos = tr->curpos;
290                 tri->lastprog = now;
291             }
292 #ifdef NOTIFY
293             if((tr->state = DC_TRNS_MAIN) && (now - tri->lastprog > 600)) {
294                 if(dcpid == 0)
295                     notify(&trnote, "transfer.error", _("Transfer stalled"), _("The transfer of %ls from %ls has not made progress for 10 minutes"), getfilename(tr->path), tr->peernick);
296             }
297 #endif
298             if((tr->state == DC_TRNS_MAIN) && (dnow - tri->sprog > 10)) {
299                 tri->speed = ((double)(tr->curpos - tri->spos) / (dnow - tri->sprog));
300                 tri->spos = tr->curpos;
301                 tri->sprog = dnow;
302             }
303         }
304     }
305     updatetooltip();
306 }
307
308 void trlscb(int resp, void *data)
309 {
310     updatetrinfo();
311 }
312
313 gint trupdatecb(gpointer data)
314 {
315     updatetrinfo();
316     return(TRUE);
317 }
318
319 void logincb(int err, wchar_t *reason, void *data)
320 {
321     if(err != DC_LOGIN_ERR_SUCCESS) {
322         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
323         exit(1);
324     }
325     dc_queuecmd(NULL, NULL, L"notify", L"trans:act", L"on", L"trans:prog", L"on", NULL);
326     dc_gettrlistasync(trlscb, NULL);
327     connected = 1;
328     updatewrite();
329 }
330
331 void dcfdcb(gpointer data, gint source, GdkInputCondition cond)
332 {
333     struct dc_response *resp;
334     
335     if(((cond & GDK_INPUT_READ) && dc_handleread()) || ((cond & GDK_INPUT_WRITE) && dc_handlewrite())) {
336         if(errno == 0) {
337             gtk_main_quit();
338         } else {
339             msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server: %s"), strerror(errno));
340             exit(1);
341         }
342         return;
343     }
344     while((resp = dc_getresp()) != NULL) {
345         if(!wcscmp(resp->cmdname, L".connect")) {
346             if(resp->code != 201) {
347                 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server refused the connection"));
348                 exit(1);
349             } else if(dc_checkprotocol(resp, DC_LATEST)) {
350                 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Server protocol revision mismatch"));
351                 exit(1);
352             } else {
353                 gtk_status_icon_set_tooltip(tray, _("Authenticating..."));
354                 dc_loginasync(NULL, 1, dc_convnone, logincb, NULL);
355             }
356         } else if(!wcscmp(resp->cmdname, L".notify")) {
357             dc_uimisc_handlenotify(resp);
358             updatetrinfo();
359         }
360         dc_freeresp(resp);
361     }
362     updatewrite();
363 }
364
365 void updatewrite(void)
366 {
367     if(dcfd < 0)
368         return;
369     if(dc_wantwrite()) {
370         if(dcfdwtag == -1)
371             dcfdwtag = gdk_input_add(dcfd, GDK_INPUT_WRITE, dcfdcb, NULL);
372     } else {
373         if(dcfdwtag != -1) {
374             gdk_input_remove(dcfdwtag);
375             dcfdwtag = -1;
376         }
377     }
378 }
379
380 void connectdc(void)
381 {
382     if((dcfd = dc_connect(server)) < 0) {
383         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server: %s"), strerror(errno));
384         exit(1);
385     }
386     dcfdrtag = gdk_input_add(dcfd, GDK_INPUT_READ, dcfdcb, NULL);
387     updatewrite();
388     gtk_status_icon_set_tooltip(tray, _("Connecting to server..."));
389 }
390
391 void startdaemon(void)
392 {
393     char pf[1024];
394     int pfd[2], i;
395     
396     if(getenv("HOME") != NULL)
397         snprintf(pf, sizeof(pf), "%s/.doldacond.pid", getenv("HOME"));
398     else
399         snprintf(pf, sizeof(pf), "%s/.doldacond.pid", getpwuid(getuid())->pw_dir);
400     if(access(pf, F_OK) || !running(pf)) {
401         pipe(pfd);
402         if((dpid = fork()) == 0) {
403             dup2(pfd[1], 2);
404             for(i = 3; i < FD_SETSIZE; i++)
405                 close(i);
406             execlp("doldacond", "doldacond", "-p", pf, NULL);
407             perror("doldacond");
408             exit(127);
409         }
410         if(dpid == -1)
411             abort();
412         close(pfd[1]);
413         derrfd = pfd[0];
414         derrtag = gdk_input_add(derrfd, GDK_INPUT_READ, derrcb, NULL);
415         create_start_wnd();
416         gtk_widget_show(start_wnd);
417         gtk_status_icon_set_tooltip(tray, _("Starting..."));
418     } else {
419         connectdc();
420     }
421 }
422
423 gboolean daemonized(gpointer uu)
424 {
425     gtk_widget_hide(start_wnd);
426     dpid = 0;
427     if(derrfd != -1) {
428         gdk_input_remove(derrtag);
429         close(derrfd);
430     }
431     if(dstat != 0) {
432         gtk_status_icon_set_visible(tray, FALSE);
433         gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(start_log)), derrbuf, derrbufdata);
434         gtk_widget_show(start_errwnd);
435     } else {
436         connectdc();
437     }
438     return(FALSE);
439 }
440
441 void sighandler(int sig)
442 {
443     pid_t p;
444     int status;
445     
446     if(sig == SIGCHLD) {
447         while((p = waitpid(-1, &status, WNOHANG)) > 0) {
448             if(p == dpid) {
449                 dstat = status;
450                 gtk_timeout_add(1, daemonized, NULL);
451             } else if(p == dcpid) {
452                 dcpid = 0;
453             }
454         }
455     }
456 }
457
458 void dolcon(void)
459 {
460     int i;
461     
462     if((dcpid = fork()) == 0) {
463         for(i = 3; i < FD_SETSIZE; i++)
464             close(i);
465         if(remote)
466             execlp("dolcon", "dolcon", NULL);
467         else
468             execlp("dolcon", "dolcon", "-l", NULL);
469         perror("dolcon");
470         exit(127);
471     }
472 }
473
474 void cb_shm_dolconf_activate(GtkWidget *uu1, gpointer uu2)
475 {
476     int i;
477     
478     if((dcpid = fork()) == 0) {
479         for(i = 3; i < FD_SETSIZE; i++)
480             close(i);
481         execlp("dolconf", "dolconf", NULL);
482         perror("dolconf");
483         exit(127);
484     }
485 }
486
487 void cb_shm_dolcon_activate(GtkWidget *uu1, gpointer uu2)
488 {
489     dolcon();
490 }
491
492 void cb_shm_quit_activate(GtkWidget *uu1, gpointer uu2)
493 {
494     dc_queuecmd(NULL, NULL, L"shutdown", NULL);
495     updatewrite();
496 }
497
498 void tray_activate(GtkStatusIcon *uu1, gpointer uu2)
499 {
500     if(dpid != 0) {
501         gtk_widget_show(start_wnd);
502     } else if(connected) {
503         dolcon();
504     }
505 }
506
507 void tray_popup(GtkStatusIcon *uu1, guint button, guint time, gpointer uu2)
508 {
509     gtk_menu_popup(GTK_MENU(shm_menu), NULL, NULL, NULL, NULL, button, time);
510 }
511
512 void cb_start_hide_clicked(GtkWidget *uu1, gpointer uu2)
513 {
514     gtk_widget_hide(start_wnd);
515 }
516
517 void cb_start_abort_clicked(GtkWidget *uu1, gpointer uu2)
518 {
519     kill(dpid, SIGINT);
520     exit(0);
521 }
522
523 void cb_start_errok_clicked(GtkWidget *uu1, gpointer uu2)
524 {
525     gtk_main_quit();
526 }
527
528 #include "../dolda-icon.xpm"
529
530 void inittray(void)
531 {
532     tray = gtk_status_icon_new_from_pixbuf(gdk_pixbuf_scale_simple(dcicon, 24, 24, GDK_INTERP_BILINEAR));
533     gtk_status_icon_set_tooltip(tray, "");
534     g_signal_connect(G_OBJECT(tray), "activate", G_CALLBACK(tray_activate), (gpointer)NULL);
535     g_signal_connect(G_OBJECT(tray), "popup-menu", G_CALLBACK(tray_popup), (gpointer)NULL);
536 }
537
538 int main(int argc, char **argv)
539 {
540     int c;
541     
542     setlocale(LC_ALL, "");
543     bindtextdomain(PACKAGE, LOCALEDIR);
544     textdomain(PACKAGE);
545     signal(SIGCHLD, sighandler);
546     dc_init();
547     server = dc_srv_local;
548     gtk_init(&argc, &argv);
549 #ifdef HAVE_NOTIFY
550     notify_init("Dolda Connect");
551 #endif
552     while((c = getopt(argc, argv, "rhs:")) != -1) {
553         switch(c) {
554         case 'r':
555             remote = 1;
556             server = NULL;
557             break;
558         case 's':
559             remote = 1;
560             server = optarg;
561             break;
562         case 'h':
563             printf("usage: doldacond-shell [-hr]\n");
564             printf("\t-h\tDisplay this help message\n");
565             printf("\t-r\tConnect to a remote host\n");
566             exit(0);
567         default:
568             fprintf(stderr, "usage: doldacond-shell [-hr]\n");
569             exit(1);
570         }
571     }
572
573     create_shm_wnd();
574     dcicon = gdk_pixbuf_new_from_xpm_data((const char **)dolda_icon_xpm);
575     gtk_window_set_default_icon(dcicon);
576     inittray();
577     if(remote)
578         connectdc();
579     else
580         startdaemon();
581     
582     g_timeout_add(10000, trupdatecb, NULL);
583     gtk_main();
584
585     return(0);
586 }
587
588 #include "dsh-start.gtk"
589 #include "dsh-menu.gtk"