Display transfer stats in dsh tray icon tooltip.
[doldaconnect.git] / clients / gui-shell / dsh.c
CommitLineData
34d45a15
FT
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
47struct trinfo {
48 int ostate;
0d12617c 49 int opos, spos, speed;
fa9a8ac1 50 time_t lastprog;
0d12617c 51 double sprog;
34d45a15
FT
52};
53
54void updatewrite(void);
55
71aeadfc 56int remote = 0;
34d45a15
FT
57GtkStatusIcon *tray;
58pid_t dpid = 0, dcpid = 0;
59int connected = 0;
60int dstat;
61int derrfd, derrtag;
62char *derrbuf = NULL;
63size_t derrbufsize = 0, derrbufdata = 0;
64int dcfd, dcfdrtag, dcfdwtag = -1;
65GdkPixbuf *dcicon;
66#ifdef HAVE_NOTIFY
67NotifyNotification *trnote = NULL;
68#endif
69
70#include "dsh-start.gtkh"
71#include "dsh-menu.gtkh"
72
73int 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
90void 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
107int 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
124void destroytr(struct dc_transfer *tr)
125{
126 struct trinfo *tri;
127
128 tri = tr->udata;
129 free(tri);
130}
131
132void 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;
0d12617c
FT
139 tri->spos = tri->opos = tr->curpos;
140 tri->speed = -1;
fa9a8ac1 141 tri->lastprog = time(NULL);
0d12617c 142 tri->sprog = ntime();
34d45a15
FT
143}
144
145#ifdef HAVE_NOTIFY
146void 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
fa9a8ac1
FT
164/* XXX: Achtung! Too DC-specific! */
165wchar_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
0d12617c
FT
175char *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
199void updatetooltip(void)
200{
201 struct dc_transfer *tr;
202 struct trinfo *tri;
203 int t, i, a, st;
204 char *buf;
205 size_t bufsize, bufdata;
206
207 t = i = a = 0;
208 st = -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) && (tri->speed != -1)) {
219 if(st == -1)
220 st = 0;
221 st += tri->speed;
222 }
223 }
224 buf = NULL;
225 bufsize = bufdata = 0;
226 bprintf(buf, "Transfers: %i", t);
227 if(t > 0)
228 bprintf(buf, " (%i/%i)", i, a);
229 if(st != -1) {
230 bprintf(buf, ", %s/s", bytes2si(st));
231 }
232 addtobuf(buf, 0);
233 gtk_status_icon_set_tooltip(tray, buf);
234 free(buf);
235}
236
34d45a15
FT
237void trstatechange(struct dc_transfer *tr, int ostate)
238{
0d12617c
FT
239 struct trinfo *tri;
240
241 tri = tr->udata;
fa9a8ac1 242 if((ostate == DC_TRNS_MAIN) && (tr->dir == DC_TRNSD_DOWN)) {
34d45a15
FT
243 if(tr->state == DC_TRNS_DONE) {
244#ifdef HAVE_NOTIFY
43edea91
FT
245 if(dcpid == 0)
246 notify(&trnote, "transfer.complete", _("Transfer complete"), _("Finished downloading %ls from %ls"), getfilename(tr->path), tr->peernick);
34d45a15
FT
247#endif
248 } else {
249#ifdef HAVE_NOTIFY
43edea91
FT
250 if(dcpid == 0)
251 notify(&trnote, "transfer.error", _("Transfer interrupted"), _("The transfer of %ls from %ls was interrupted from the other side"), getfilename(tr->path), tr->peernick);
34d45a15
FT
252#endif
253 }
254 }
0d12617c
FT
255 if(tr->state == DC_TRNS_MAIN) {
256 tri->speed = -1;
257 tri->spos = tr->curpos;
258 tri->sprog = ntime();
259 }
34d45a15
FT
260}
261
262void updatetrinfo(void)
263{
264 struct dc_transfer *tr;
265 struct trinfo *tri;
fa9a8ac1 266 time_t now;
0d12617c 267 double dnow;
34d45a15 268
fa9a8ac1 269 now = time(NULL);
0d12617c 270 dnow = ntime();
34d45a15
FT
271 for(tr = dc_transfers; tr != NULL; tr = tr->next) {
272 if(tr->udata == NULL) {
273 inittr(tr);
274 } else {
275 tri = tr->udata;
276 if(tri->ostate != tr->state) {
277 trstatechange(tr, tri->ostate);
278 tri->ostate = tr->state;
279 }
fa9a8ac1
FT
280 if(tri->opos != tr->curpos) {
281 tri->opos = tr->curpos;
282 tri->lastprog = now;
283 }
284#ifdef NOTIFY
285 if((tr->state = DC_TRNS_MAIN) && (now - tri->lastprog > 600)) {
43edea91
FT
286 if(dcpid == 0)
287 notify(&trnote, "transfer.error", _("Transfer stalled"), _("The transfer of %ls from %ls has not made progress for 10 minutes"), getfilename(tr->path), tr->peernick);
fa9a8ac1
FT
288 }
289#endif
0d12617c
FT
290 if((tr->state == DC_TRNS_MAIN) && (dnow - tri->sprog > 10)) {
291 tri->speed = ((double)(tr->curpos - tri->spos) / (dnow - tri->sprog));
292 tri->spos = tr->curpos;
293 tri->sprog = dnow;
294 }
34d45a15
FT
295 }
296 }
0d12617c 297 updatetooltip();
34d45a15
FT
298}
299
300void trlscb(int resp, void *data)
301{
302 updatetrinfo();
303}
304
0d12617c
FT
305gint trupdatecb(gpointer data)
306{
307 updatetrinfo();
308 return(TRUE);
309}
310
34d45a15
FT
311void logincb(int err, wchar_t *reason, void *data)
312{
313 if(err != DC_LOGIN_ERR_SUCCESS) {
314 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
315 exit(1);
316 }
34d45a15
FT
317 dc_queuecmd(NULL, NULL, L"notify", L"trans:act", L"on", L"trans:prog", L"on", NULL);
318 dc_gettrlistasync(trlscb, NULL);
319 connected = 1;
320 updatewrite();
321}
322
323void dcfdcb(gpointer data, gint source, GdkInputCondition cond)
324{
325 struct dc_response *resp;
326
327 if(((cond & GDK_INPUT_READ) && dc_handleread()) || ((cond & GDK_INPUT_WRITE) && dc_handlewrite())) {
328 if(errno == 0) {
329 gtk_main_quit();
330 } else {
331 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server: %s"), strerror(errno));
332 exit(1);
333 }
334 return;
335 }
336 while((resp = dc_getresp()) != NULL) {
337 if(!wcscmp(resp->cmdname, L".connect")) {
338 if(resp->code != 201) {
339 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server refused the connection"));
340 exit(1);
341 } else if(dc_checkprotocol(resp, DC_LATEST)) {
342 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Server protocol revision mismatch"));
343 exit(1);
344 } else {
345 gtk_status_icon_set_tooltip(tray, _("Authenticating..."));
346 dc_loginasync(NULL, 1, dc_convnone, logincb, NULL);
347 }
348 } else if(!wcscmp(resp->cmdname, L".notify")) {
349 dc_uimisc_handlenotify(resp);
350 updatetrinfo();
351 }
d20e3861 352 dc_freeresp(resp);
34d45a15
FT
353 }
354 updatewrite();
355}
356
357void updatewrite(void)
358{
359 if(dcfd < 0)
360 return;
361 if(dc_wantwrite()) {
362 if(dcfdwtag == -1)
363 dcfdwtag = gdk_input_add(dcfd, GDK_INPUT_WRITE, dcfdcb, NULL);
364 } else {
365 if(dcfdwtag != -1) {
366 gdk_input_remove(dcfdwtag);
367 dcfdwtag = -1;
368 }
369 }
370}
371
372void connectdc(void)
373{
71aeadfc 374 if((dcfd = dc_connect(remote?NULL:dc_srv_local)) < 0) {
34d45a15
FT
375 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server: %s"), strerror(errno));
376 exit(1);
377 }
378 dcfdrtag = gdk_input_add(dcfd, GDK_INPUT_READ, dcfdcb, NULL);
379 updatewrite();
380 gtk_status_icon_set_tooltip(tray, _("Connecting to server..."));
381}
382
383void startdaemon(void)
384{
385 char pf[1024];
386 int pfd[2], i;
387
388 if(getenv("HOME") != NULL)
389 snprintf(pf, sizeof(pf), "%s/.doldacond.pid", getenv("HOME"));
390 else
391 snprintf(pf, sizeof(pf), "%s/.doldacond.pid", getpwuid(getuid())->pw_dir);
392 if(access(pf, F_OK) || !running(pf)) {
393 pipe(pfd);
394 if((dpid = fork()) == 0) {
395 dup2(pfd[1], 2);
396 for(i = 3; i < FD_SETSIZE; i++)
397 close(i);
398 execlp("doldacond", "doldacond", "-p", pf, NULL);
399 perror("doldacond");
400 exit(127);
401 }
402 if(dpid == -1)
403 abort();
404 close(pfd[1]);
405 derrfd = pfd[0];
406 derrtag = gdk_input_add(derrfd, GDK_INPUT_READ, derrcb, NULL);
407 create_start_wnd();
408 gtk_widget_show(start_wnd);
409 gtk_status_icon_set_tooltip(tray, _("Starting..."));
410 } else {
411 connectdc();
412 }
413}
414
415gboolean daemonized(gpointer uu)
416{
417 gtk_widget_hide(start_wnd);
418 dpid = 0;
419 if(derrfd != -1) {
420 gdk_input_remove(derrtag);
421 close(derrfd);
422 }
423 if(dstat != 0) {
424 gtk_status_icon_set_visible(tray, FALSE);
425 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(start_log)), derrbuf, derrbufdata);
426 gtk_widget_show(start_errwnd);
427 } else {
428 connectdc();
429 }
430 return(FALSE);
431}
432
433void sighandler(int sig)
434{
435 pid_t p;
436 int status;
437
438 if(sig == SIGCHLD) {
439 while((p = waitpid(-1, &status, WNOHANG)) > 0) {
440 if(p == dpid) {
441 dstat = status;
442 gtk_timeout_add(1, daemonized, NULL);
443 } else if(p == dcpid) {
444 dcpid = 0;
445 }
446 }
447 }
448}
449
450void dolcon(void)
451{
452 int i;
453
454 if((dcpid = fork()) == 0) {
455 for(i = 3; i < FD_SETSIZE; i++)
456 close(i);
71aeadfc
FT
457 if(remote)
458 execlp("dolcon", "dolcon", NULL);
459 else
460 execlp("dolcon", "dolcon", "-l", NULL);
34d45a15
FT
461 perror("dolcon");
462 exit(127);
463 }
464}
465
31a01fdd
FT
466void cb_shm_dolconf_activate(GtkWidget *uu1, gpointer uu2)
467{
468 int i;
469
470 if((dcpid = fork()) == 0) {
471 for(i = 3; i < FD_SETSIZE; i++)
472 close(i);
473 execlp("dolconf", "dolconf", NULL);
474 perror("dolconf");
475 exit(127);
476 }
477}
478
34d45a15
FT
479void cb_shm_dolcon_activate(GtkWidget *uu1, gpointer uu2)
480{
481 dolcon();
482}
483
484void cb_shm_quit_activate(GtkWidget *uu1, gpointer uu2)
485{
486 dc_queuecmd(NULL, NULL, L"shutdown", NULL);
487 updatewrite();
488}
489
490void tray_activate(GtkStatusIcon *uu1, gpointer uu2)
491{
492 if(dpid != 0) {
493 gtk_widget_show(start_wnd);
494 } else if(connected) {
495 dolcon();
496 }
497}
498
499void tray_popup(GtkStatusIcon *uu1, guint button, guint time, gpointer uu2)
500{
501 gtk_menu_popup(GTK_MENU(shm_menu), NULL, NULL, NULL, NULL, button, time);
502}
503
504void cb_start_hide_clicked(GtkWidget *uu1, gpointer uu2)
505{
506 gtk_widget_hide(start_wnd);
507}
508
509void cb_start_abort_clicked(GtkWidget *uu1, gpointer uu2)
510{
511 kill(dpid, SIGINT);
512 exit(0);
513}
514
515void cb_start_errok_clicked(GtkWidget *uu1, gpointer uu2)
516{
517 gtk_main_quit();
518}
519
520#include "../dolda-icon.xpm"
521
522void inittray(void)
523{
524 tray = gtk_status_icon_new_from_pixbuf(gdk_pixbuf_scale_simple(dcicon, 24, 24, GDK_INTERP_BILINEAR));
525 gtk_status_icon_set_tooltip(tray, "");
526 g_signal_connect(G_OBJECT(tray), "activate", G_CALLBACK(tray_activate), (gpointer)NULL);
527 g_signal_connect(G_OBJECT(tray), "popup-menu", G_CALLBACK(tray_popup), (gpointer)NULL);
528}
529
530int main(int argc, char **argv)
531{
71aeadfc
FT
532 int c;
533
34d45a15
FT
534 setlocale(LC_ALL, "");
535 bindtextdomain(PACKAGE, LOCALEDIR);
536 textdomain(PACKAGE);
537 signal(SIGCHLD, sighandler);
538 dc_init();
539 gtk_init(&argc, &argv);
540#ifdef HAVE_NOTIFY
541 notify_init("Dolda Connect");
542#endif
71aeadfc
FT
543 while((c = getopt(argc, argv, "rh")) != -1) {
544 switch(c) {
545 case 'r':
546 remote = 1;
547 break;
548 case 'h':
549 printf("usage: doldacond-shell [-hr]\n");
550 printf("\t-h\tDisplay this help message\n");
551 printf("\t-r\tConnect to a remote host\n");
552 exit(0);
553 default:
554 fprintf(stderr, "usage: doldacond-shell [-hr]\n");
555 exit(1);
556 }
557 }
34d45a15
FT
558
559 create_shm_wnd();
560 dcicon = gdk_pixbuf_new_from_xpm_data((const char **)dolda_icon_xpm);
561 gtk_window_set_default_icon(dcicon);
562 inittray();
71aeadfc
FT
563 if(remote)
564 connectdc();
565 else
566 startdaemon();
34d45a15 567
0d12617c 568 g_timeout_add(10000, trupdatecb, NULL);
34d45a15
FT
569 gtk_main();
570
571 return(0);
572}
573
574#include "dsh-start.gtk"
575#include "dsh-menu.gtk"