1 /*
2  * display.c:
3  * Display images gathered by driftnet.
4  *
5  * Copyright (c) 2001 Chris Lightfoot.
6  * Email: chris@ex-parrot.com; WWW: http://www.ex-parrot.com/~chris/
7  *
8  */
9 
10 #ifdef HAVE_CONFIG_H
11     #include <config.h>
12 #endif
13 
14 #include "compat/compat.h"
15 
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <stdio.h>
19 #include <stdlib.h> /* On many systems (Darwin...), stdio.h is a prerequisite. */
20 #include <string.h>
21 #include <unistd.h>
22 
23 #include <gtk/gtk.h>
24 #include <gdk/gdk.h>
25 #include <gdk/gdkx.h>
26 
27 #include <sys/stat.h>
28 
29 #include "common/util.h"
30 #include "common/log.h"
31 #include "common/tmpdir.h"
32 #include "img.h"
33 
34 #include "display.h"
35 
36 /* The border, in pixels, around images displayed in the window. */
37 #define BORDER  6
38 #define DEFAULT_WIDTH   320
39 #define DEFAULT_HEIGHT  240
40 
41 static int imgpipe_readfd;
42 static int imgpipe_writefd;
43 static int beep_on_image;
44 static char *savedimg_prefix;
45 
46 static GtkWidget *window, *darea;
47 static GdkWindow *drawable;
48 
49 static int width, height, wrx, wry, rowheight;
50 static img backing_image;
51 
52 struct imgrect {
53     char *filename;
54     int x, y, w, h;
55 };
56 
57 static int nimgrects;
58 static struct imgrect *imgrects;
59 
60 static void do_gtkdisplay(void);
61 
display_send_img(const char * name,size_t len)62 void display_send_img(const char *name, size_t len)
63 {
64     write(imgpipe_writefd, name, len);
65 }
66 
do_image_display(char * img_prefix,int beep)67 void do_image_display(char *img_prefix, int beep)
68 {
69     /* PID of display child and file descriptor on pipe to same. */
70     pid_t dpychld;
71     int pfd[2];
72 
73     pipe(pfd);
74 
75     switch (dpychld = fork()) {
76         case 0:
77             /* we are the child */
78             close (pfd[1]);
79             imgpipe_readfd  = pfd[0];
80             beep_on_image   = beep;
81             savedimg_prefix = img_prefix;
82             do_gtkdisplay();
83             abort(); /* TODO: exit ¿? */ /* not reached */
84 
85         case -1:
86             log_msg(LOG_ERROR, "fork failed, reason: %s", strerror(errno));
87             //log_msg(LOG_FATAL, "fork failed");
88             abort(); /* TODO: exit ¿? */
89 
90         default:
91             close (pfd[0]);
92             imgpipe_writefd  = pfd[1];
93             log_msg(LOG_INFO, "started display child, pid %d", (int)dpychld);
94             return;
95     }
96 
97     return;
98 }
99 
delete_event(GtkWidget * widget,GdkEvent * event,gpointer data)100 gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) {
101     log_msg(LOG_INFO, "display child shutting down\n");
102     return FALSE;   /* do destroy window */
103 }
104 
105 /* make_backing_image:
106  * Create the img structure which represents our back-buffer. */
make_backing_image()107 void make_backing_image() {
108     img I;
109     I = img_new_blank(width, height);
110     img_alloc(I);
111 
112     if (backing_image) {
113         int w2, h2;
114         struct imgrect *ir;
115 
116         /* Copy old contents of backing image to ll corner of new one. */
117         w2 = backing_image->width;
118         if (w2 > width) w2 = width;
119         h2 = backing_image->height;
120         if (h2 > height) h2 = height;
121 
122         img_simple_blt(I, 0, height - h2, backing_image, 0, backing_image->height - h2, w2, h2);
123 
124         /* Move all of the image rectangles. */
125         for (ir = imgrects; ir < imgrects + nimgrects; ++ir) {
126             if (ir->filename) {
127                 ir->y += height - backing_image->height;
128 
129                 /* Possible it has scrolled off the window. */
130                 if (ir->x > width || ir->y + ir->h < 0) {
131                     unlink(ir->filename);
132                     xfree(ir->filename);
133                     memset(ir, 0, sizeof *ir);
134                 }
135             }
136         }
137 
138         /* Adjust placement of new images. */
139         if (wrx >= w2) wrx = w2;
140 
141         img_delete(backing_image);
142     }
143     backing_image = I;
144     wrx = BORDER;
145     wry = height - BORDER;
146     rowheight = 2 * BORDER;
147 }
148 
149 /* update_window:
150  * Copy the backing image onto the window. */
update_window()151 void update_window() {
152     if (backing_image) {
153         GdkGC *gc;
154         gc = gdk_gc_new(drawable);
155         gdk_draw_rgb_32_image(drawable, gc, 0, 0, width, height, GDK_RGB_DITHER_NORMAL, (guchar*)backing_image->flat, sizeof(pel) * width);
156         g_object_unref(gc);
157     }
158 }
159 
160 /* scroll_backing_image:
161  * Scroll the image up a bit, to make room for a new image. */
scroll_backing_image(const int dy)162 void scroll_backing_image(const int dy) {
163     pel **row1, **row2;
164     struct imgrect *ir;
165 
166     for (row1 = backing_image->data, row2 = backing_image->data + dy;
167          row2 < backing_image->data + height; ++row1, ++row2)
168         memcpy(*row1, *row2, width * sizeof(pel));
169 
170     for (row2 = row1; row2 < backing_image->data + height; ++row2)
171         memset(*row2, 0, width * sizeof(pel));
172 
173     for (ir = imgrects; ir < imgrects + nimgrects; ++ir) {
174         if (ir->filename) {
175             ir->y -= dy;
176 
177             /* scrolled off bottom, no longer in use. */
178             if ((ir->y + ir->h) < 0) {
179                 unlink(ir->filename);
180                 xfree(ir->filename);
181                 memset(ir, 0, sizeof *ir);
182             }
183         }
184     }
185 }
186 
187 /* add_image_rectangle:
188  * Add a rectangle representing the location of an image to the list, so that
189  * we can do hit-tests against it. */
add_image_rectangle(const char * filename,const int x,const int y,const int w,const int h)190 void add_image_rectangle(const char *filename, const int x, const int y, const int w, const int h) {
191     struct imgrect *ir;
192     for (ir = imgrects; ir < imgrects + nimgrects; ++ir) {
193         if (!ir->filename)
194             break;
195     }
196     if (ir == imgrects + nimgrects) {
197         imgrects = xrealloc(imgrects, 2 * nimgrects * sizeof *imgrects);
198         memset(imgrects + nimgrects, 0, nimgrects * sizeof *imgrects);
199         ir = imgrects + nimgrects;
200         nimgrects *= 2;
201     }
202     ir->filename = strdup(filename);
203     ir->x = x;
204     ir->y = y;
205     ir->w = w;
206     ir->h = h;
207 }
208 
209 /* find_image_rectangle:
210  * Find the image, if any, which contains a given point. Used for saving images
211  * when they are clicked on. */
find_image_rectangle(const int x,const int y)212 struct imgrect *find_image_rectangle(const int x, const int y) {
213     struct imgrect *ir;
214     for (ir = imgrects; ir < imgrects + nimgrects; ++ir)
215         if (ir->filename && x >= ir->x && x < ir->x + ir->w && y >= ir->y && y < ir->y + ir->h)
216             return ir;
217     return NULL;
218 }
219 
220 /* expose_event:
221  * React to an expose event, perhaps changing the backing image size. */
expose_event(GtkWidget * widget,GdkEvent * event,gpointer data)222 void expose_event(GtkWidget *widget, GdkEvent *event, gpointer data) {
223     if (darea) drawable = darea->window;
224     gdk_drawable_get_size(GDK_DRAWABLE(drawable), &width, &height);
225     if (!backing_image || backing_image->width != width || backing_image->height != height)
226         make_backing_image();
227 
228     update_window();
229 }
230 
231 /* configure_event:
232  * React to a configure event, perhaps changing the backing image size. */
configure_event(GtkWidget * widget,GdkEvent * event,gpointer data)233 void configure_event(GtkWidget *widget, GdkEvent *event, gpointer data) {
234     if (darea) drawable = darea->window;
235     gdk_drawable_get_size(GDK_DRAWABLE(drawable), &width, &height);
236     if (!backing_image || backing_image->width != width || backing_image->height != height)
237         make_backing_image();
238 
239     update_window();
240 }
241 
242 /* save_image:
243  * Save an image which the user has selected. */
save_image(struct imgrect * ir)244 void save_image(struct imgrect *ir) {
245     static char *name;
246     static int num;
247     int fd1, fd2;
248     char buf[8192];
249     ssize_t l;
250     struct stat st;
251 
252     if (!name)
253         name = xcalloc(strlen(savedimg_prefix) + 16, 1);
254 
255     do
256         sprintf(name, "%s%d%s", savedimg_prefix, num++, strrchr(ir->filename, '.'));
257     while (stat(name, &st) == 0);
258     log_msg(LOG_INFO, "saving `%s' as `%s'", ir->filename, name);
259 
260     fd1 = open(ir->filename, O_RDONLY);
261     if (fd1 == -1) {
262         log_msg(LOG_ERROR, "%s: %s", ir->filename, strerror(errno));
263         return;
264     }
265 
266     fd2 = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666);
267     if (fd2 == -1) {
268         log_msg(LOG_ERROR, "%s: %s", name, strerror(errno));
269         close(fd1);
270         return;
271     }
272 
273     /* XXX interrupts */
274     while ((l = read(fd1, buf, sizeof buf)) > 0) {
275         if (write(fd2, buf, l) == -1) {
276             log_msg(LOG_ERROR, "%s: %s", name, strerror(errno));
277             close(fd1);
278             close(fd2);
279             return;
280         }
281     }
282 
283     if (l == -1)
284         log_msg(LOG_ERROR, "%s: %s", ir->filename, strerror(errno));
285 
286     close(fd1);
287     close(fd2);
288 }
289 
290 static struct {
291     int x, y;
292 } click;
293 
button_press_event(GtkWidget * widget,GdkEventButton * event)294 void button_press_event(GtkWidget *widget, GdkEventButton *event) {
295     click.x = (int)event->x;
296     click.y = (int)event->y;
297 }
298 
button_release_event(GtkWidget * widget,GdkEventButton * event)299 void button_release_event(GtkWidget *widget, GdkEventButton *event) {
300     struct imgrect *ir;
301     ir = find_image_rectangle(click.x, click.y);
302     if (ir && ir == find_image_rectangle((int)event->x, (int)event->y)) {
303         /* We draw a little frame around the image while we're saving it, to
304          * give some visual feedback. */
305         gdk_draw_rectangle(drawable, darea->style->white_gc, 0, ir->x - 2, ir->y - 2, ir->w + 3, ir->h + 3);
306         gdk_flush();    /* force X to actually draw the damn thing. */
307         save_image(ir);
308         xnanosleep(100000000);
309         gdk_draw_rectangle(drawable, darea->style->black_gc, 0, ir->x - 2, ir->y - 2, ir->w + 3, ir->h + 3);
310     }
311 }
312 
destroy(GtkWidget * widget,gpointer data)313 void destroy(GtkWidget *widget, gpointer data) {
314     gtk_main_quit();
315 }
316 
317 /* xread:
318  * Like read(2) but read the whole supplied length. */
xread(int fd,void * buf,size_t len)319 static ssize_t xread(int fd, void *buf, size_t len) {
320     char *p;
321     for (p = (char*)buf; p < (char*)buf + len; ) {
322         ssize_t l;
323         l = read(fd, p, (char*)buf + len - p);
324         if (l == -1 && errno != EINTR)
325             return -1;
326         else if (l == 0)
327             return 0;
328         else
329             p += l;
330     }
331     return len;
332 }
333 
pipe_event(GIOChannel chan,GIOCondition cond,gpointer data)334 gboolean pipe_event(GIOChannel chan, GIOCondition cond, gpointer data) {
335     static char *path;
336     char name[TMPNAMELEN];
337     ssize_t rr;
338     int nimgs = 0;
339 
340     if (!path)
341         path = xmalloc(strlen(get_tmpdir()) + TMPNAMELEN);
342 
343     /* We are sent messages of size TMPNAMELEN containing a null-terminated
344      * file name. */
345     while (nimgs < 4 && (rr = xread(imgpipe_readfd, name, sizeof name)) == sizeof name) {
346         int saveimg = 0;
347         struct stat st;
348 
349         ++nimgs;
350 
351         sprintf(path, "%s/%s", get_tmpdir(), name);
352 
353         if (stat(path, &st) == -1)
354             continue;
355 
356         log_msg(LOG_INFO, "received image %s of size %d", name, (int)st.st_size);
357         /* Check to see whether this looks like an image we're interested in. */
358         if (st.st_size > 100) {
359             /* Small images are probably bollocks. */
360             img i = img_new();
361             if (!img_load_file(i, path, header, unknown))
362                 log_msg(LOG_WARNING, "%s: bogus image (err = %d)", name, i->err);
363             else {
364                 if (i->width > 8 && i->height > 8) {
365                     if (img_load(i, full, i->type)) {
366                         /* slot in the new image at some plausible place. */
367                         int w, h;
368                         if (i->width > width - 2 * BORDER) w = width - 2 * BORDER;
369                         else w = i->width;
370                         if (i->height > height - 2 * BORDER) h = height - 2 * BORDER;
371                         else h = i->height;
372 
373                         /* is there space on this row? */
374                         if (width - wrx < w) {
375                             /* no */
376                             scroll_backing_image(h + BORDER);
377                             wrx = BORDER;
378                             rowheight = h + BORDER;
379                         }
380                         if (rowheight < h + BORDER) {
381                             scroll_backing_image(h + BORDER - rowheight);
382                             rowheight = h + BORDER;
383                         }
384 
385                         img_simple_blt(backing_image, wrx, wry - h, i, 0, 0, w, h);
386                         add_image_rectangle(path, wrx, wry - h, w, h);
387                         saveimg = 1;
388 
389                         if (beep_on_image)
390                             write(1, "\a", 1);
391 
392                         update_window();
393 
394                         wrx += w + BORDER;
395                     } else log_msg(LOG_WARNING, "%s: bogus image (err = %d)", name, i->err);
396                 } else log_msg(LOG_WARNING, "%s: image dimensions (%d x %d) too small to bother with", name, i->width, i->height);
397             }
398 
399             img_delete(i);
400         } else log_msg(LOG_WARNING, "image data too small (%d bytes) to bother with", (int)st.st_size);
401 
402         if (!saveimg)
403             unlink(name);
404     }
405     if (rr == -1 && errno != EINTR && errno != EAGAIN) {
406         log_msg(LOG_ERROR, "display pipe read() failed, reason: %s", strerror(errno));
407         gtk_main_quit();
408 
409     } else if (rr == 0) {
410         /* pipe closed, exit. */
411         gtk_main_quit();
412     }
413     return TRUE;
414 }
415 
do_gtkdisplay(void)416 static void do_gtkdisplay(void)
417 {
418     GIOChannel *chan;
419     struct imgrect *ir;
420 
421     /* have our main loop poll the pipe file descriptor */
422     chan = g_io_channel_unix_new(imgpipe_readfd);
423     g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP, (GIOFunc)pipe_event, NULL);
424     fcntl(imgpipe_readfd, F_SETFL, O_NONBLOCK);
425 
426     /* set up list of image rectangles. */
427     imgrects = xcalloc(nimgrects = 16, sizeof *imgrects);
428 
429     /* do some init thing */
430     gtk_init(0, NULL);
431 
432     gtk_widget_push_colormap(gdk_rgb_get_colormap());
433 
434     /* Make our own window. */
435     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
436     gtk_widget_set_size_request(window, DEFAULT_WIDTH + 2 * BORDER, DEFAULT_HEIGHT + 2 * BORDER);
437 
438     darea = gtk_drawing_area_new();
439     gtk_container_add(GTK_CONTAINER(window), darea);
440     gtk_widget_set_events(darea, GDK_EXPOSURE_MASK|GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK);
441 
442     g_signal_connect(G_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(delete_event), NULL);
443     g_signal_connect(G_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(destroy), NULL);
444 
445     g_signal_connect(G_OBJECT(darea), "expose-event", GTK_SIGNAL_FUNC(expose_event), NULL);
446     g_signal_connect(G_OBJECT(darea), "configure_event", GTK_SIGNAL_FUNC(expose_event), NULL);
447 
448     /* mouse button press/release for saving images */
449     g_signal_connect(G_OBJECT(darea), "button_press_event", GTK_SIGNAL_FUNC(button_press_event), NULL);
450     g_signal_connect(G_OBJECT(darea), "button_release_event", GTK_SIGNAL_FUNC(button_release_event), NULL);
451 
452     gtk_widget_show_all(window);
453 
454     gtk_main();
455 
456     /* Get rid of all remaining images. */
457     for (ir = imgrects; ir < imgrects + nimgrects; ++ir)
458         if (ir->filename)
459             unlink(ir->filename);
460 
461     img_delete(backing_image);
462 
463     gtk_exit(0);
464 
465     return; /* NOTREACHED */
466 }
467