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