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