Mercurial > pidgin
view src/rvous.c @ 221:b2f9f629525e
[gaim-migrate @ 231]
Added on the beginnings of file sending. Win clients can now figure out which
file you're *trying* to send them, but can't get it.
The AIM method of Get File is really fscked up, IMHO. I don't know if any of
you are familiar with it, but I'll describe it. Some user decides they want
to download a file from another user, so they choose Get File. It then returns
a list of files available from that person, and they can choose which ones they
want to download.
The other person can't decide on a user-by-user basis which files are listed,
only if any files are listed at all (not allowing people to download them).
The way I'm going to implement it is when someone gets a message that another
person is trying to download a file from them, it asks them which file they
want to make available. You can only do one file at a time this way, but that's
tough if you want to be downloading more than one file. Use gnutella or FTP or
something that's better designed for it than AIM. But the way the win AIM
clients are now, it acts as a public ftp server, and I think it shouldn't.
committer: Tailor Script <tailor@pidgin.im>
author | Eric Warmenhoven <eric@warmenhoven.org> |
---|---|
date | Sun, 07 May 2000 01:50:06 +0000 |
parents | 1a5ee1f8b39e |
children | 6ced2f1c8b24 |
line wrap: on
line source
/* * gaim * * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include <string.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <gtk/gtk.h> #include "gaim.h" static void do_send_file(GtkWidget *, struct file_transfer *); static void do_get_file (GtkWidget *, struct file_transfer *); static void toggle(GtkWidget *w, int *m) { *m = !(*m); } static void free_ft(struct file_transfer *ft) { if (ft->window) { gtk_widget_destroy(ft->window); ft->window = NULL; } if (ft->filename) g_free(ft->filename); if (ft->user) g_free(ft->user); if (ft->message) g_free(ft->message); if (ft->ip) g_free(ft->ip); if (ft->cookie) g_free(ft->cookie); g_free(ft); } static void warn_callback(GtkWidget *widget, struct file_transfer *ft) { show_warn_dialog(ft->user); } static void info_callback(GtkWidget *widget, struct file_transfer *ft) { serv_get_info(ft->user); } static void cancel_callback(GtkWidget *widget, struct file_transfer *ft) { char *send = g_malloc(256); if (ft->accepted) { g_free(send); return; } g_snprintf(send, 255, "toc_rvous_cancel %s %s %s", normalize(ft->user), ft->cookie, ft->UID); sflap_send(send, strlen(send), TYPE_DATA); g_free(send); free_ft(ft); } static void accept_callback(GtkWidget *widget, struct file_transfer *ft) { char *buf = g_malloc(BUF_LEN); char *fname = g_malloc(BUF_LEN); char *c; if (!strcmp(ft->UID, FILE_SEND_UID)) { c = ft->filename + strlen(ft->filename); while (c != ft->filename) { if (*c == '/' || *c == '\\') { strcpy(fname, c+1); break; } c--; } if (c == ft->filename) strcpy(fname, ft->filename); } gtk_widget_destroy(ft->window); ft->window = NULL; ft->window = gtk_file_selection_new("Gaim - Save As..."); gtk_file_selection_hide_fileop_buttons(GTK_FILE_SELECTION(ft->window)); if (!strcmp(ft->UID, FILE_SEND_UID)) g_snprintf(buf, BUF_LEN - 1, "%s/%s", getenv("HOME"), fname); else g_snprintf(buf, BUF_LEN - 1, "%s/", getenv("HOME")); gtk_file_selection_set_filename(GTK_FILE_SELECTION(ft->window), buf); gtk_signal_connect(GTK_OBJECT(ft->window), "destroy", GTK_SIGNAL_FUNC(cancel_callback), ft); if (!strcmp(ft->UID, FILE_SEND_UID)) { gtk_signal_connect(GTK_OBJECT( GTK_FILE_SELECTION(ft->window)->ok_button), "clicked", GTK_SIGNAL_FUNC(do_get_file), ft); } else { gtk_signal_connect(GTK_OBJECT( GTK_FILE_SELECTION(ft->window)->ok_button), "clicked", GTK_SIGNAL_FUNC(do_send_file), ft); } gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(ft->window)->cancel_button), "clicked", GTK_SIGNAL_FUNC(cancel_callback), ft); gtk_widget_show(ft->window); g_free(buf); g_free(fname); } static int read_file_header(int fd, struct file_header *header) { char buf[257]; int read_rv = read(fd, buf, 256); memcpy(&header->magic, buf + 0, 4); memcpy(&header->hdrlen, buf + 4, 2); memcpy(&header->hdrtype, buf + 6, 2); memcpy(&header->bcookie, buf + 8, 8); memcpy(&header->encrypt, buf + 16, 2); memcpy(&header->compress, buf + 18, 2); memcpy(&header->totfiles, buf + 20, 2); memcpy(&header->filesleft, buf + 22, 2); memcpy(&header->totparts, buf + 24, 2); memcpy(&header->partsleft, buf + 26, 2); memcpy(&header->totsize, buf + 28, 4); memcpy(&header->size, buf + 32, 4); memcpy(&header->modtime, buf + 36, 4); memcpy(&header->checksum, buf + 40, 4); memcpy(&header->rfrcsum, buf + 44, 4); memcpy(&header->rfsize, buf + 48, 4); memcpy(&header->cretime, buf + 52, 4); memcpy(&header->rfcsum, buf + 56, 4); memcpy(&header->nrecvd, buf + 60, 4); memcpy(&header->recvcsum, buf + 64, 4); memcpy(&header->idstring, buf + 68, 32); memcpy(&header->flags, buf + 100, 1); memcpy(&header->lnameoffset, buf + 101, 1); memcpy(&header->lsizeoffset, buf + 102, 1); memcpy(&header->dummy, buf + 103, 69); memcpy(&header->macfileinfo, buf + 172, 16); memcpy(&header->nencode, buf + 188, 2); memcpy(&header->nlanguage, buf + 190, 2); memcpy(&header->name, buf + 192, 64); return read_rv; } static int write_file_header(int fd, struct file_header *header) { char buf[257]; memcpy(buf + 0, &header->magic, 4); memcpy(buf + 4, &header->hdrlen, 2); memcpy(buf + 6, &header->hdrtype, 2); memcpy(buf + 8, &header->bcookie, 8); memcpy(buf + 16, &header->encrypt, 2); memcpy(buf + 18, &header->compress, 2); memcpy(buf + 20, &header->totfiles, 2); memcpy(buf + 22, &header->filesleft, 2); memcpy(buf + 24, &header->totparts, 2); memcpy(buf + 26, &header->partsleft, 2); memcpy(buf + 28, &header->totsize, 4); memcpy(buf + 32, &header->size, 4); memcpy(buf + 36, &header->modtime, 4); memcpy(buf + 40, &header->checksum, 4); memcpy(buf + 44, &header->rfrcsum, 4); memcpy(buf + 48, &header->rfsize, 4); memcpy(buf + 52, &header->cretime, 4); memcpy(buf + 56, &header->rfcsum, 4); memcpy(buf + 60, &header->nrecvd, 4); memcpy(buf + 64, &header->recvcsum, 4); memcpy(buf + 68, &header->idstring, 32); memcpy(buf + 100, &header->flags, 1); memcpy(buf + 101, &header->lnameoffset, 1); memcpy(buf + 102, &header->lsizeoffset, 1); memcpy(buf + 103, &header->dummy, 69); memcpy(buf + 172, &header->macfileinfo, 16); memcpy(buf + 188, &header->nencode, 2); memcpy(buf + 190, &header->nlanguage, 2); memcpy(buf + 192, &header->name, 64); return write(fd, buf, 256); } static void do_get_file(GtkWidget *w, struct file_transfer *ft) { char *send = g_malloc(256); char *file = gtk_file_selection_get_filename(GTK_FILE_SELECTION(ft->window)); char *buf; struct file_header *header = g_new0(struct file_header, 1); int read_rv; guint32 rcv; char *c; int cont = 1; GtkWidget *fw = NULL, *fbar = NULL, *label = NULL; GtkWidget *button = NULL, *pct = NULL; if (!(ft->f = fopen(file,"w"))) { buf = g_malloc(BUF_LONG); g_snprintf(buf, BUF_LONG / 2, "Error writing file %s", file); do_error_dialog(buf, "Error"); g_free(buf); g_free(header); ft->accepted = 0; accept_callback(NULL, ft); return; } ft->accepted = 1; gtk_widget_destroy(ft->window); ft->window = NULL; g_snprintf(send, 255, "toc_rvous_accept %s %s %s", normalize(ft->user), ft->cookie, ft->UID); sflap_send(send, strlen(send), TYPE_DATA); g_free(send); ft->fd = connect_address(inet_addr(ft->ip), ft->port); if (ft->fd <= -1) { g_free(header); fclose(ft->f); free_ft(ft); return; } read_rv = read_file_header(ft->fd, header); if(read_rv < 0) { close(ft->fd); fclose(ft->f); g_free(header); free_ft(ft); return; } sprintf(debug_buff, "header length %d\n", header->hdrlen); debug_print(debug_buff); header->hdrtype = 0x202; buf = frombase64(ft->cookie); memcpy(header->bcookie, buf, 8); g_free(buf); snprintf(header->idstring, 32, "Gaim"); header->encrypt = 0; header->compress = 0; header->totparts = 1; header->partsleft = 1; sprintf(debug_buff, "sending confirmation\n"); debug_print(debug_buff); write_file_header(ft->fd, header); buf = g_malloc(2048); rcv = 0; fw = gtk_dialog_new(); buf = g_malloc(2048); snprintf(buf, 2048, "Receiving %s from %s", ft->filename, ft->user); label = gtk_label_new(buf); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->vbox), label, 0, 0, 5); gtk_widget_show(label); fbar = gtk_progress_bar_new(); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), fbar, 0, 0, 5); gtk_widget_show(fbar); pct = gtk_label_new("0 %"); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), pct, 0, 0, 5); gtk_widget_show(pct); button = gtk_button_new_with_label("Cancel"); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), button, 0, 0, 5); gtk_widget_show(button); gtk_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc)toggle, &cont); gtk_window_set_title(GTK_WINDOW(fw), "Gaim - File Transfer"); gtk_widget_realize(fw); aol_icon(fw->window); gtk_widget_show(fw); sprintf(debug_buff, "Receiving %s from %s (%d bytes)\n", ft->filename, ft->user, ft->size); debug_print(debug_buff); fcntl(ft->fd, F_SETFL, O_NONBLOCK); while (rcv != ft->size && cont) { int i; float pcnt = ((float)rcv)/((float)ft->size); int remain = ft->size - rcv > 1024 ? 1024 : ft->size - rcv; read_rv = read(ft->fd, buf, remain); if(read_rv < 0) { if (errno == EWOULDBLOCK) { while(gtk_events_pending()) gtk_main_iteration(); continue; } gtk_widget_destroy(fw); fw = NULL; fclose(ft->f); close(ft->fd); g_free(buf); g_free(header); free_ft(ft); return; } rcv += read_rv; for (i = 0; i < read_rv; i++) fprintf(ft->f, "%c", buf[i]); gtk_progress_bar_update(GTK_PROGRESS_BAR(fbar), pcnt); sprintf(buf, "%d / %d K (%2.0f %%)", rcv/1024, ft->size/1024, 100*pcnt); gtk_label_set_text(GTK_LABEL(pct), buf); while(gtk_events_pending()) gtk_main_iteration(); } fclose(ft->f); gtk_widget_destroy(fw); fw = NULL; if (!cont) { char *tmp = frombase64(ft->cookie); sprintf(buf, "toc_rvous_cancel %s %s %s", ft->user, tmp, ft->UID); sflap_send(buf, strlen(buf), TYPE_DATA); close(ft->fd); free_ft(ft); g_free(header); return; } sprintf(debug_buff, "Download complete.\n"); debug_print(debug_buff); header->hdrtype = 0x402; header->totparts = 0; header->partsleft = 0; header->flags = 0; header->recvcsum = header->checksum; header->nrecvd = header->totsize; write_file_header(ft->fd, header); close(ft->fd); g_free(buf); g_free(header); free_ft(ft); } static void do_send_file(GtkWidget *w, struct file_transfer *ft) { char *send = g_malloc(256); char *file = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(ft->window))); char *buf; int read_rv; struct file_header *fhdr = g_new0(struct file_header, 1); struct sockaddr_in sin; guint32 rcv; int cont; char *c; struct stat st; struct tm *fortime; GtkWidget *fw = NULL, *fbar = NULL, *label = NULL; GtkWidget *button = NULL, *pct = NULL; stat(file, &st); if (!(ft->f = fopen(file, "r"))) { buf = g_malloc(BUF_LONG); g_snprintf(buf, BUF_LONG / 2, "Error reading file %s", file); do_error_dialog(buf, "Error"); g_free(buf); ft->accepted = 0; accept_callback(NULL, ft); free_ft(ft); return; } ft->accepted = 1; gtk_widget_destroy(ft->window); ft->window = NULL; g_snprintf(send, 255, "toc_rvous_accept %s %s %s", normalize(ft->user), ft->cookie, ft->UID); sflap_send(send, strlen(send), TYPE_DATA); g_free(send); ft->fd = connect_address(inet_addr(ft->ip), ft->port); if (ft->fd <= -1) { g_free(fhdr); free_ft(ft); return; } /* here's where we differ from do_get_file */ /* 1. build/send header * 2. receive header * 3. send listing file * 4. receive header * * then we need to wait to actually send the file. * * 5. either (receive header) or (remote host cancels) * 6. if received header, send file * 7. ? haven't gotten this far yet */ /* 1. build/send header */ c = file + strlen(file); while (*(c - 1) != '/') c--; buf = frombase64(ft->cookie); sprintf(debug_buff, "Building header to send %s (cookie: %s)\n", file, buf); debug_print(debug_buff); fhdr->magic[0] = 'O'; fhdr->magic[1] = 'F'; fhdr->magic[2] = 'T'; fhdr->magic[3] = '2'; fhdr->hdrlen = htons(256); fhdr->hdrtype = htons(0x1108); snprintf(fhdr->bcookie, 8, "%s", buf); g_free(buf); fhdr->encrypt = 0; fhdr->compress = 0; fhdr->totfiles = htons(1); fhdr->filesleft = htons(1); fhdr->totparts = htons(1); fhdr->partsleft = htons(1); fhdr->totsize = htonl((long)st.st_size); /* combined size of all files */ /* size = strlen("mm/dd/yyyy hh:mm sizesize 'name'\r\n") */ fhdr->size = htonl(28 + strlen(c)); /* size of listing.txt */ fhdr->modtime = htonl(time(NULL)); /* time since UNIX epoch */ fhdr->checksum = htonl(0x89f70000); /* ? i don't think this matters */ fhdr->rfrcsum = 0; fhdr->rfsize = 0; fhdr->cretime = 0; fhdr->rfcsum = 0; fhdr->nrecvd = 0; fhdr->recvcsum = 0; snprintf(fhdr->idstring, 32, "OFT_Windows ICBMFT V1.1 32"); fhdr->flags = 0x02; /* don't ask me why */ fhdr->lnameoffset = 0x1A; /* ? still no clue */ fhdr->lsizeoffset = 0x10; /* whatever */ memset(fhdr->dummy, 0, 69); memset(fhdr->macfileinfo, 0, 16); fhdr->nencode = 0; fhdr->nlanguage = 0; snprintf(fhdr->name, 64, "listing.txt"); read_rv = write_file_header(ft->fd, fhdr); if (read_rv <= -1) { sprintf(debug_buff, "Couldn't write opening header\n"); debug_print(debug_buff); g_free(fhdr); close(ft->fd); free_ft(ft); return; } /* 2. receive header */ sprintf(debug_buff, "Receiving header\n"); debug_print(debug_buff); read_rv = read_file_header(ft->fd, fhdr); if (read_rv <= -1) { sprintf(debug_buff, "Couldn't read header\n"); debug_print(debug_buff); g_free(fhdr); close(ft->fd); free_ft(ft); return; } /* 3. qend listing file */ /* mm/dd/yyyy hh:mm sizesize name.ext\r\n */ /* creation date ^ */ sprintf(debug_buff, "Sending file\n"); debug_print(debug_buff); buf = g_malloc(ft->size + 1); fortime = localtime(&st.st_ctime); snprintf(buf, ntohl(fhdr->size) + 1, "%2d/%2d/%4d %2d:%2d %8ld %s\r\n", fortime->tm_mon + 1, fortime->tm_mday, fortime->tm_year + 1900, fortime->tm_hour + 1, fortime->tm_min + 1, st.st_size, c); sprintf(debug_buff, "Sending listing.txt (%ld bytes) to %s\n", ntohl(fhdr->size) + 1, ft->user); debug_print(debug_buff); read_rv = write(ft->fd, buf, ntohl(fhdr->size)); if (read_rv <= -1) { sprintf(debug_buff, "Could not send file, wrote %d\n", rcv); debug_print(debug_buff); g_free(buf); g_free(fhdr); close(ft->fd); free_ft(ft); return; } g_free(buf); /* 4. receive header */ sprintf(debug_buff, "Receiving closing header\n"); debug_print(debug_buff); read_rv = read_file_header(ft->fd, fhdr); if (read_rv <= -1) { sprintf(debug_buff, "Couldn't read closing header\n"); debug_print(debug_buff); g_free(fhdr); close(ft->fd); free_ft(ft); return; } /* 5. wait to see if we're sending it */ fcntl(ft->fd, F_SETFL, O_NONBLOCK); rcv = 0; while (rcv != 256) { int i; read_rv = read_file_header(ft->fd, fhdr); if(read_rv < 0) { if (errno == EWOULDBLOCK) { while(gtk_events_pending()) gtk_main_iteration(); continue; } fclose(ft->f); close(ft->fd); g_free(fhdr); free_ft(ft); return; } rcv += read_rv; } /* 6. send the file */ fw = gtk_dialog_new(); buf = g_malloc(2048); snprintf(buf, 2048, "Sending %s to %s", fhdr->name, ft->user); label = gtk_label_new(buf); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->vbox), label, 0, 0, 5); gtk_widget_show(label); fbar = gtk_progress_bar_new(); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), fbar, 0, 0, 5); gtk_widget_show(fbar); pct = gtk_label_new("0 %"); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), pct, 0, 0, 5); gtk_widget_show(pct); button = gtk_button_new_with_label("Cancel"); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), button, 0, 0, 5); gtk_widget_show(button); gtk_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc)toggle, &cont); gtk_window_set_title(GTK_WINDOW(fw), "Gaim - File Transfer"); gtk_widget_realize(fw); aol_icon(fw->window); gtk_widget_show(fw); sprintf(debug_buff, "Sending %s to %s (%d bytes)\n", fhdr->name, ft->user, fhdr->totsize); debug_print(debug_buff); rcv = 0; cont = 1; while (rcv != fhdr->totsize && cont) { int i; float pcnt = ((float)rcv)/((float)fhdr->totsize); int remain = fhdr->totsize - rcv > 1024 ? 1024 : fhdr->totsize - rcv; for (i = 0; i < 1024; i++) fscanf(ft->f, "%c", &buf[i]); read_rv = write(ft->fd, buf, remain); if (read_rv < 0) { if (errno == EWOULDBLOCK) { while (gtk_events_pending()) gtk_main_iteration(); continue; } fclose(ft->f); gtk_widget_destroy(fw); fw = NULL; fclose(ft->f); close(ft->fd); g_free(buf); g_free(fhdr); free_ft(ft); return; } rcv += read_rv; gtk_progress_bar_update(GTK_PROGRESS_BAR(fbar), pcnt); sprintf(buf, "%d / %d K (%2.0f %%)", rcv/1024, ft->size/1024, 100*pcnt); gtk_label_set_text(GTK_LABEL(pct), buf); while(gtk_events_pending()) gtk_main_iteration(); } fclose(ft->f); gtk_widget_destroy(fw); fw = NULL; if (!cont) { char *tmp = frombase64(ft->cookie); sprintf(buf, "toc_rvous_cancel %s %s %s", ft->user, tmp, ft->UID); sflap_send(buf, strlen(buf), TYPE_DATA); g_free(buf); close(ft->fd); free_ft(ft); g_free(fhdr); return; } sprintf(debug_buff, "Upload complete.\n"); debug_print(debug_buff); /* 7. receive closing header */ /* done */ close(ft->fd); g_free(buf); g_free(fhdr); free_ft(ft); } void accept_file_dialog(struct file_transfer *ft) { GtkWidget *accept, *info, *warn, *cancel; GtkWidget *label; GtkWidget *vbox, *bbox; char buf[1024]; ft->window = gtk_window_new(GTK_WINDOW_DIALOG); accept = gtk_button_new_with_label("Accept"); info = gtk_button_new_with_label("Info"); warn = gtk_button_new_with_label("Warn"); cancel = gtk_button_new_with_label("Cancel"); bbox = gtk_hbox_new(TRUE, 10); vbox = gtk_vbox_new(FALSE, 5); gtk_widget_show(accept); gtk_widget_show(info); gtk_widget_show(warn); gtk_widget_show(cancel); gtk_box_pack_start(GTK_BOX(bbox), accept, TRUE, TRUE, 10); gtk_box_pack_start(GTK_BOX(bbox), info, TRUE, TRUE, 10); gtk_box_pack_start(GTK_BOX(bbox), warn, TRUE, TRUE, 10); gtk_box_pack_start(GTK_BOX(bbox), cancel, TRUE, TRUE, 10); if (!strcmp(ft->UID, FILE_SEND_UID)) { g_snprintf(buf, sizeof(buf), "%s requests you to accept the file: %s (%d bytes)", ft->user, ft->filename, ft->size); } else { g_snprintf(buf, sizeof(buf), "%s requests you to send them a file", ft->user); } if (ft->message) strncat(buf, ft->message, sizeof(buf) - strlen(buf)); label = gtk_label_new(buf); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 5); gtk_box_pack_start(GTK_BOX(vbox), bbox, TRUE, TRUE, 5); gtk_window_set_title(GTK_WINDOW(ft->window), "Gaim - File Transfer?"); gtk_window_set_focus(GTK_WINDOW(ft->window), accept); gtk_container_add(GTK_CONTAINER(ft->window), vbox); gtk_container_border_width(GTK_CONTAINER(ft->window), 10); gtk_widget_show(vbox); gtk_widget_show(bbox); gtk_widget_realize(ft->window); aol_icon(ft->window->window); gtk_widget_show(ft->window); gtk_signal_connect(GTK_OBJECT(accept), "clicked", GTK_SIGNAL_FUNC(accept_callback), ft); gtk_signal_connect(GTK_OBJECT(cancel), "clicked", GTK_SIGNAL_FUNC(cancel_callback), ft); gtk_signal_connect(GTK_OBJECT(warn), "clicked", GTK_SIGNAL_FUNC(warn_callback), ft); gtk_signal_connect(GTK_OBJECT(info), "clicked", GTK_SIGNAL_FUNC(info_callback), ft); if (ft->message) { /* we'll do this later while(gtk_events_pending()) gtk_main_iteration(); html_print(text, ft->message); */ } }