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