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