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