Make tooltip translatable.
[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;
0c095756 203 int t, i, a, st, bc, bt;
0d12617c
FT
204 char *buf;
205 size_t bufsize, bufdata;
206
207 t = i = a = 0;
0c095756 208 st = bc = bt = -1;
0d12617c
FT
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++;
0c095756
FT
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 }
0d12617c
FT
228 }
229 }
230 buf = NULL;
231 bufsize = bufdata = 0;
f782eb97 232 bprintf(buf, "%s: %i", _("Transfers"), t);
0d12617c
FT
233 if(t > 0)
234 bprintf(buf, " (%i/%i)", i, a);
0c095756
FT
235 if(bt > 0)
236 bprintf(buf, ", %.1f%%", (double)bc / (double)bt);
237 if(st != -1)
0d12617c 238 bprintf(buf, ", %s/s", bytes2si(st));
0d12617c
FT
239 addtobuf(buf, 0);
240 gtk_status_icon_set_tooltip(tray, buf);
241 free(buf);
242}
243
34d45a15
FT
244void trstatechange(struct dc_transfer *tr, int ostate)
245{
0d12617c
FT
246 struct trinfo *tri;
247
248 tri = tr->udata;
fa9a8ac1 249 if((ostate == DC_TRNS_MAIN) && (tr->dir == DC_TRNSD_DOWN)) {
34d45a15
FT
250 if(tr->state == DC_TRNS_DONE) {
251#ifdef HAVE_NOTIFY
43edea91
FT
252 if(dcpid == 0)
253 notify(&trnote, "transfer.complete", _("Transfer complete"), _("Finished downloading %ls from %ls"), getfilename(tr->path), tr->peernick);
34d45a15
FT
254#endif
255 } else {
256#ifdef HAVE_NOTIFY
43edea91
FT
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);
34d45a15
FT
259#endif
260 }
261 }
0d12617c
FT
262 if(tr->state == DC_TRNS_MAIN) {
263 tri->speed = -1;
264 tri->spos = tr->curpos;
265 tri->sprog = ntime();
266 }
34d45a15
FT
267}
268
269void updatetrinfo(void)
270{
271 struct dc_transfer *tr;
272 struct trinfo *tri;
fa9a8ac1 273 time_t now;
0d12617c 274 double dnow;
34d45a15 275
fa9a8ac1 276 now = time(NULL);
0d12617c 277 dnow = ntime();
34d45a15
FT
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 }
fa9a8ac1
FT
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)) {
43edea91
FT
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);
fa9a8ac1
FT
295 }
296#endif
0d12617c
FT
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 }
34d45a15
FT
302 }
303 }
0d12617c 304 updatetooltip();
34d45a15
FT
305}
306
307void trlscb(int resp, void *data)
308{
309 updatetrinfo();
310}
311
0d12617c
FT
312gint trupdatecb(gpointer data)
313{
314 updatetrinfo();
315 return(TRUE);
316}
317
34d45a15
FT
318void 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 }
34d45a15
FT
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
330void 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 }
d20e3861 359 dc_freeresp(resp);
34d45a15
FT
360 }
361 updatewrite();
362}
363
364void 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
379void connectdc(void)
380{
71aeadfc 381 if((dcfd = dc_connect(remote?NULL:dc_srv_local)) < 0) {
34d45a15
FT
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
390void 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
422gboolean 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
440void 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
457void dolcon(void)
458{
459 int i;
460
461 if((dcpid = fork()) == 0) {
462 for(i = 3; i < FD_SETSIZE; i++)
463 close(i);
71aeadfc
FT
464 if(remote)
465 execlp("dolcon", "dolcon", NULL);
466 else
467 execlp("dolcon", "dolcon", "-l", NULL);
34d45a15
FT
468 perror("dolcon");
469 exit(127);
470 }
471}
472
31a01fdd
FT
473void 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
34d45a15
FT
486void cb_shm_dolcon_activate(GtkWidget *uu1, gpointer uu2)
487{
488 dolcon();
489}
490
491void cb_shm_quit_activate(GtkWidget *uu1, gpointer uu2)
492{
493 dc_queuecmd(NULL, NULL, L"shutdown", NULL);
494 updatewrite();
495}
496
497void 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
506void 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
511void cb_start_hide_clicked(GtkWidget *uu1, gpointer uu2)
512{
513 gtk_widget_hide(start_wnd);
514}
515
516void cb_start_abort_clicked(GtkWidget *uu1, gpointer uu2)
517{
518 kill(dpid, SIGINT);
519 exit(0);
520}
521
522void cb_start_errok_clicked(GtkWidget *uu1, gpointer uu2)
523{
524 gtk_main_quit();
525}
526
527#include "../dolda-icon.xpm"
528
529void 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
537int main(int argc, char **argv)
538{
71aeadfc
FT
539 int c;
540
34d45a15
FT
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
71aeadfc
FT
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 }
34d45a15
FT
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();
71aeadfc
FT
570 if(remote)
571 connectdc();
572 else
573 startdaemon();
34d45a15 574
0d12617c 575 g_timeout_add(10000, trupdatecb, NULL);
34d45a15
FT
576 gtk_main();
577
578 return(0);
579}
580
581#include "dsh-start.gtk"
582#include "dsh-menu.gtk"