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