]> git.meshlink.io Git - catta/blob - avahi-ui/avahi-ui.c
avahi-ui: fix segfault when browsing for services in non-existing domains or domains...
[catta] / avahi-ui / avahi-ui.c
1 /* $Id$ */
2
3 /***
4   This file is part of avahi.
5  
6   avahi is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as
8   published by the Free Software Foundation; either version 2.1 of the
9   License, or (at your option) any later version.
10  
11   avahi is distributed in the hope that it will be useful, but WITHOUT
12   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14   Public License for more details.
15  
16   You should have received a copy of the GNU Lesser General Public
17   License along with avahi; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <string.h>
27 #include <stdarg.h>
28 #include <net/if.h>
29
30 #include <gtk/gtk.h>
31
32 #include <avahi-glib/glib-watch.h>
33 #include <avahi-client/client.h>
34 #include <avahi-client/lookup.h>
35 #include <avahi-common/error.h>
36 #include <avahi-common/address.h>
37 #include <avahi-common/domain.h>
38
39 #include "avahi-ui.h"
40
41 #if defined(HAVE_GDBM) || defined(HAVE_DBM)
42 #include "../avahi-utils/stdb.h"
43 #endif
44
45 /* todo: i18n, HIGify */
46
47 struct _AuiServiceDialogPrivate {
48     AvahiGLibPoll *glib_poll;
49     AvahiClient *client;
50     AvahiServiceBrowser **browsers;
51     AvahiServiceResolver *resolver;
52     AvahiDomainBrowser *domain_browser;
53     
54     gchar **browse_service_types;
55     gchar *service_type;
56     gchar *domain;
57     gchar *service_name;
58     AvahiProtocol address_family;
59         
60     AvahiAddress address;
61     gchar *host_name;
62     AvahiStringList *txt_data;
63     guint16 port;
64
65     gboolean resolve_service, resolve_service_done;
66     gboolean resolve_host_name, resolve_host_name_done;
67     
68     GtkWidget *domain_label;
69     GtkWidget *domain_button;
70     GtkWidget *service_tree_view;
71     GtkWidget *service_progress_bar;
72
73     GtkListStore *service_list_store, *domain_list_store;
74
75     guint service_pulse_timeout;
76     guint domain_pulse_timeout;
77     guint start_idle;
78
79     AvahiIfIndex common_interface;
80     AvahiProtocol common_protocol;
81
82     GtkWidget *domain_dialog;
83     GtkWidget *domain_entry;
84     GtkWidget *domain_tree_view;
85     GtkWidget *domain_progress_bar;
86     GtkWidget *domain_ok_button;
87 };
88
89 enum {
90     PROP_0,
91     PROP_BROWSE_SERVICE_TYPES,
92     PROP_DOMAIN,
93     PROP_SERVICE_TYPE,
94     PROP_SERVICE_NAME,
95     PROP_ADDRESS,
96     PROP_PORT,
97     PROP_HOST_NAME,
98     PROP_TXT_DATA,
99     PROP_RESOLVE_SERVICE,
100     PROP_RESOLVE_HOST_NAME,
101     PROP_ADDRESS_FAMILY
102 };
103
104 enum {
105     SERVICE_COLUMN_IFACE,
106     SERVICE_COLUMN_PROTO,
107     SERVICE_COLUMN_TYPE,
108     SERVICE_COLUMN_NAME,
109     SERVICE_COLUMN_PRETTY_IFACE,
110     SERVICE_COLUMN_PRETTY_TYPE,
111     N_SERVICE_COLUMNS
112 };
113
114 enum {
115     DOMAIN_COLUMN_NAME,
116     DOMAIN_COLUMN_REF,
117     N_DOMAIN_COLUMNS
118 };
119
120 static void aui_service_dialog_finalize(GObject *object);
121 static void aui_service_dialog_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
122 static void aui_service_dialog_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
123
124 G_DEFINE_TYPE(AuiServiceDialog, aui_service_dialog, GTK_TYPE_DIALOG)
125
126 static void aui_service_dialog_class_init(AuiServiceDialogClass *klass) {
127     GObjectClass *object_class;
128     
129     object_class = (GObjectClass*) klass;
130
131     object_class->finalize = aui_service_dialog_finalize;
132     object_class->set_property = aui_service_dialog_set_property;
133     object_class->get_property = aui_service_dialog_get_property;
134
135     g_object_class_install_property(
136             object_class,
137             PROP_BROWSE_SERVICE_TYPES,
138             g_param_spec_pointer("browse_service_types", "Browse Service Types", "A NULL terminated list of service types to browse for",
139                                 G_PARAM_READABLE | G_PARAM_WRITABLE));
140     g_object_class_install_property(
141             object_class,
142             PROP_DOMAIN,
143             g_param_spec_string("domain", "Domain", "The domain to browse in, or NULL for the default domain",
144                                 NULL,
145                                 G_PARAM_READABLE | G_PARAM_WRITABLE));
146     g_object_class_install_property(
147             object_class,
148             PROP_SERVICE_TYPE,
149             g_param_spec_string("service_type", "Service Type", "The service type of the selected service",
150                                 NULL,
151                                 G_PARAM_READABLE | G_PARAM_WRITABLE));
152     g_object_class_install_property(
153             object_class,
154             PROP_SERVICE_NAME,
155             g_param_spec_string("service_name", "Service Name", "The service name of the selected service",
156                                 NULL,
157                                 G_PARAM_READABLE | G_PARAM_WRITABLE));
158     g_object_class_install_property(
159             object_class,
160             PROP_ADDRESS,
161             g_param_spec_pointer("address", "Address", "The address of the resolved service",
162                                 G_PARAM_READABLE));    
163     g_object_class_install_property(
164             object_class,
165             PROP_PORT,
166             g_param_spec_uint("port", "Port", "The IP port number of the resolved service",
167                              0, 0xFFFF, 0,
168                              G_PARAM_READABLE));
169     g_object_class_install_property(
170             object_class,
171             PROP_HOST_NAME,
172             g_param_spec_string("host_name", "Host Name", "The host name of the resolved service",
173                                 NULL,
174                                 G_PARAM_READABLE));
175     g_object_class_install_property(
176             object_class,
177             PROP_TXT_DATA,
178             g_param_spec_pointer("txt_data", "TXT Data", "The TXT data of the resolved service",
179                                 G_PARAM_READABLE));
180     g_object_class_install_property(
181             object_class,
182             PROP_RESOLVE_SERVICE,
183             g_param_spec_boolean("resolve_service", "Resolve service", "Resolve service",
184                                  TRUE,
185                                  G_PARAM_READABLE | G_PARAM_WRITABLE));
186     g_object_class_install_property(
187             object_class,
188             PROP_RESOLVE_HOST_NAME,
189             g_param_spec_boolean("resolve_host_name", "Resolve service host name", "Resolve service host name",
190                                  TRUE,
191                                  G_PARAM_READABLE | G_PARAM_WRITABLE));
192     g_object_class_install_property(
193             object_class,
194             PROP_ADDRESS_FAMILY,
195             g_param_spec_int("address_family", "Address family", "The address family for host name resolution",
196                              AVAHI_PROTO_UNSPEC, AVAHI_PROTO_INET6, AVAHI_PROTO_UNSPEC,
197                              G_PARAM_READABLE | G_PARAM_WRITABLE));
198 }
199
200
201 GtkWidget *aui_service_dialog_new_valist(
202         gchar *title,
203         GtkWindow *parent,
204         const gchar *first_button_text,
205         va_list varargs) {
206     
207     const gchar *button_text;
208     
209     GtkWidget *w = GTK_WIDGET(g_object_new(
210                                       AUI_TYPE_SERVICE_DIALOG,
211                                       "has-separator", FALSE,
212                                       "title", title,
213                                       NULL));
214
215     if (parent)
216         gtk_window_set_transient_for(GTK_WINDOW(w), parent);
217
218     button_text = first_button_text;
219     while (button_text) {
220         gint response_id;
221         
222         response_id = va_arg(varargs, gint);
223         gtk_dialog_add_button(GTK_DIALOG(w), button_text, response_id);
224         button_text = va_arg(varargs, const gchar *);
225     }
226     
227     return w;
228 }
229
230 GtkWidget* aui_service_dialog_new(
231         const gchar *title,
232         GtkWindow *parent,
233         const gchar *first_button_text,
234         ...) {
235
236     GtkWidget *w;
237     
238     va_list varargs;
239     va_start(varargs, first_button_text);
240     w = aui_service_dialog_new_valist((gchar*) title, parent, first_button_text, varargs);
241     va_end(varargs);
242     
243     return w;
244 }
245
246 static gboolean service_pulse_callback(gpointer data) {
247     AuiServiceDialog *d = AUI_SERVICE_DIALOG(data);
248
249     gtk_progress_bar_pulse(GTK_PROGRESS_BAR(d->priv->service_progress_bar));
250     return TRUE;
251 }
252
253 static gboolean domain_pulse_callback(gpointer data) {
254     AuiServiceDialog *d = AUI_SERVICE_DIALOG(data);
255
256     gtk_progress_bar_pulse(GTK_PROGRESS_BAR(d->priv->domain_progress_bar));
257     return TRUE;
258 }
259
260 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
261     AuiServiceDialog *d = AUI_SERVICE_DIALOG(userdata);
262
263     if (state == AVAHI_CLIENT_FAILURE) {
264         GtkWidget *m = gtk_message_dialog_new(GTK_WINDOW(d),
265                                               GTK_DIALOG_DESTROY_WITH_PARENT,
266                                               GTK_MESSAGE_ERROR,
267                                               GTK_BUTTONS_CLOSE,
268                                               "Avahi client failure: %s",
269                                               avahi_strerror(avahi_client_errno(c)));
270         gtk_dialog_run(GTK_DIALOG(m));
271         gtk_widget_destroy(m);
272         
273         gtk_dialog_response(GTK_DIALOG(d), GTK_RESPONSE_CANCEL);
274     }
275 }
276
277 static void resolve_callback(
278         AvahiServiceResolver *r,
279         AvahiIfIndex interface,
280         AvahiProtocol protocol,
281         AvahiResolverEvent event,
282         const char *name,
283         const char *type,
284         const char *domain,
285         const char *host_name,
286         const AvahiAddress *a,
287         uint16_t port,
288         AvahiStringList *txt,
289         AvahiLookupResultFlags flags,
290         void *userdata) {
291
292     AuiServiceDialog *d = AUI_SERVICE_DIALOG(userdata);
293
294     switch (event) {
295         case AVAHI_RESOLVER_FOUND:
296
297             d->priv->resolve_service_done = 1;
298
299             g_free(d->priv->service_name);
300             d->priv->service_name = g_strdup(name);
301
302             g_free(d->priv->service_type);
303             d->priv->service_type = g_strdup(type);
304
305             g_free(d->priv->domain);
306             d->priv->domain = g_strdup(domain);
307
308             g_free(d->priv->host_name);
309             d->priv->host_name = g_strdup(host_name);
310
311             d->priv->port = port;
312
313             avahi_string_list_free(d->priv->txt_data);
314             d->priv->txt_data = avahi_string_list_copy(txt);
315             
316             if (a) {
317                 d->priv->resolve_host_name_done = 1;
318                 d->priv->address = *a;
319             }
320
321             gtk_dialog_response(GTK_DIALOG(d), GTK_RESPONSE_ACCEPT);
322
323             break;
324
325         case AVAHI_RESOLVER_FAILURE: {
326             GtkWidget *m = gtk_message_dialog_new(GTK_WINDOW(d),
327                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
328                                                   GTK_MESSAGE_ERROR,
329                                                   GTK_BUTTONS_CLOSE,
330                                                   "Avahi resolver failure: %s",
331                                                   avahi_strerror(avahi_client_errno(d->priv->client)));
332             gtk_dialog_run(GTK_DIALOG(m));
333             gtk_widget_destroy(m);
334             
335             gtk_dialog_response(GTK_DIALOG(d), GTK_RESPONSE_CANCEL);
336             break;
337         }
338     }
339 }
340
341
342 static void browse_callback(
343         AvahiServiceBrowser *b,
344         AvahiIfIndex interface,
345         AvahiProtocol protocol,
346         AvahiBrowserEvent event,
347         const char *name,
348         const char *type,
349         const char *domain,
350         AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
351         void* userdata) {
352
353     AuiServiceDialog *d = AUI_SERVICE_DIALOG(userdata);
354     
355     switch (event) {
356
357         case AVAHI_BROWSER_NEW: {
358             gchar *ifs;
359             const gchar *pretty_type;
360             char ifname[IFNAMSIZ];
361             GtkTreeIter iter;
362             GtkTreeSelection *selection;
363
364             if (!(if_indextoname(interface, ifname)))
365                 g_snprintf(ifname, sizeof(ifname), "%i", interface);
366
367             ifs = g_strdup_printf("%s %s", ifname, protocol == AVAHI_PROTO_INET ? "IPv4" : "IPv6");
368
369 #if defined(HAVE_GDBM) || defined(HAVE_DBM)
370             pretty_type = stdb_lookup(type);
371 #else
372             pretty_type = type;
373 #endif            
374             
375             gtk_list_store_append(d->priv->service_list_store, &iter);
376
377             gtk_list_store_set(d->priv->service_list_store, &iter,
378                                SERVICE_COLUMN_IFACE, interface,
379                                SERVICE_COLUMN_PROTO, protocol,
380                                SERVICE_COLUMN_NAME, name,
381                                SERVICE_COLUMN_TYPE, type,
382                                SERVICE_COLUMN_PRETTY_IFACE, ifs,
383                                SERVICE_COLUMN_PRETTY_TYPE, pretty_type,
384                                -1);
385
386             g_free(ifs);
387                     
388             if (d->priv->common_protocol == AVAHI_PROTO_UNSPEC)
389                 d->priv->common_protocol = protocol;
390
391             if (d->priv->common_interface == AVAHI_IF_UNSPEC)
392                 d->priv->common_interface = interface;
393
394             if (d->priv->common_interface != interface || d->priv->common_protocol != protocol) {
395                 gtk_tree_view_column_set_visible(gtk_tree_view_get_column(GTK_TREE_VIEW(d->priv->service_tree_view), 0), TRUE);
396                 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->priv->service_tree_view), TRUE);
397             }
398
399             selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->priv->service_tree_view));
400             if (!gtk_tree_selection_get_selected(selection, NULL, NULL)) {
401
402                 if (!d->priv->service_type ||
403                     !d->priv->service_name ||
404                     (avahi_domain_equal(d->priv->service_type, type) && strcasecmp(d->priv->service_name, name) == 0)) {
405                     GtkTreePath *path;
406                     
407                     gtk_tree_selection_select_iter(selection, &iter);
408
409                     path = gtk_tree_model_get_path(GTK_TREE_MODEL(d->priv->service_list_store), &iter);
410                     gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->priv->service_tree_view), path, NULL, FALSE);
411                     gtk_tree_path_free(path);
412                 }
413                 
414             }
415             
416             break;
417         }
418             
419         case AVAHI_BROWSER_REMOVE: {
420             GtkTreeIter iter;
421             gboolean valid;
422
423             valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(d->priv->service_list_store), &iter);
424             while (valid) {
425                 gint _interface, _protocol;
426                 gchar *_name, *_type;
427                 gboolean found;
428                 
429                 gtk_tree_model_get(GTK_TREE_MODEL(d->priv->service_list_store), &iter,
430                                    SERVICE_COLUMN_IFACE, &_interface,
431                                    SERVICE_COLUMN_PROTO, &_protocol,
432                                    SERVICE_COLUMN_NAME, &_name,
433                                    SERVICE_COLUMN_TYPE, &_type,
434                                    -1);
435
436                 found = _interface == interface && _protocol == protocol && strcasecmp(_name, name) == 0 && avahi_domain_equal(_type, type);
437                 g_free(_name);
438
439                 if (found) {
440                     gtk_list_store_remove(d->priv->service_list_store, &iter);
441                     break;
442                 }
443                 
444                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(d->priv->service_list_store), &iter);
445             }
446             
447             break;
448         }
449
450         case AVAHI_BROWSER_FAILURE: {
451             GtkWidget *m = gtk_message_dialog_new(GTK_WINDOW(d),
452                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
453                                                   GTK_MESSAGE_ERROR,
454                                                   GTK_BUTTONS_CLOSE,
455                                                   "Browsing for service type %s in domain %s failed: %s",
456                                                   type, domain,
457                                                   avahi_strerror(avahi_client_errno(d->priv->client)));
458             gtk_dialog_run(GTK_DIALOG(m));
459             gtk_widget_destroy(m);
460
461             /* Fall through */
462         }
463
464         case AVAHI_BROWSER_ALL_FOR_NOW:
465             if (d->priv->service_pulse_timeout > 0) {
466                 g_source_remove(d->priv->service_pulse_timeout);
467                 d->priv->service_pulse_timeout = 0;
468                 gtk_widget_hide(d->priv->service_progress_bar);
469             }
470             break;
471
472         case AVAHI_BROWSER_CACHE_EXHAUSTED:
473             ;
474     }
475 }
476
477 static void domain_make_default_selection(AuiServiceDialog *d, const gchar *name, GtkTreeIter *iter) {
478     GtkTreeSelection *selection;
479     
480     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(d->priv->domain_tree_view));
481     if (!gtk_tree_selection_get_selected(selection, NULL, NULL)) {
482         
483         if (avahi_domain_equal(gtk_entry_get_text(GTK_ENTRY(d->priv->domain_entry)), name)) {
484             GtkTreePath *path;
485             
486             gtk_tree_selection_select_iter(selection, iter);
487             
488             path = gtk_tree_model_get_path(GTK_TREE_MODEL(d->priv->domain_list_store), iter);
489             gtk_tree_view_set_cursor(GTK_TREE_VIEW(d->priv->domain_tree_view), path, NULL, FALSE);
490             gtk_tree_path_free(path);
491         }
492         
493     }
494 }
495
496 static void domain_browse_callback(
497         AvahiDomainBrowser *b,
498         AvahiIfIndex interface,
499         AvahiProtocol protocol,
500         AvahiBrowserEvent event,
501         const char *name,
502         AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
503         void* userdata) {
504
505     AuiServiceDialog *d = AUI_SERVICE_DIALOG(userdata);
506     
507     switch (event) {
508
509         case AVAHI_BROWSER_NEW: {
510             GtkTreeIter iter;
511             gboolean found = FALSE, valid;
512             gint ref;
513
514             valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(d->priv->domain_list_store), &iter);
515             while (valid) {
516                 gchar *_name;
517                 
518                 gtk_tree_model_get(GTK_TREE_MODEL(d->priv->domain_list_store), &iter,
519                                    DOMAIN_COLUMN_NAME, &_name,
520                                    DOMAIN_COLUMN_REF, &ref,
521                                    -1);
522
523                 found = avahi_domain_equal(_name, name);
524                 g_free(_name);
525
526                 if (found)
527                     break;
528                 
529                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(d->priv->domain_list_store), &iter);
530             }
531
532             if (found) 
533                 gtk_list_store_set(d->priv->domain_list_store, &iter, DOMAIN_COLUMN_REF, ref + 1, -1);
534             else {
535                 gtk_list_store_append(d->priv->domain_list_store, &iter);
536                 
537                 gtk_list_store_set(d->priv->domain_list_store, &iter,
538                                    DOMAIN_COLUMN_NAME, name,
539                                    DOMAIN_COLUMN_REF, 1,
540                                    -1);
541             }
542
543             domain_make_default_selection(d, name, &iter);
544             
545             break;
546         }
547
548         case AVAHI_BROWSER_REMOVE: {
549             gboolean valid;
550             GtkTreeIter iter;
551             
552             valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(d->priv->domain_list_store), &iter);
553             while (valid) {
554                 gint ref;
555                 gchar *_name;
556                 gboolean found;
557                 
558                 gtk_tree_model_get(GTK_TREE_MODEL(d->priv->domain_list_store), &iter,
559                                    DOMAIN_COLUMN_NAME, &_name,
560                                    DOMAIN_COLUMN_REF, &ref,
561                                    -1);
562
563                 found = avahi_domain_equal(_name, name);
564                 g_free(_name);
565
566                 if (found) {
567                     if (ref <= 1)
568                         gtk_list_store_remove(d->priv->service_list_store, &iter);
569                     else
570                         gtk_list_store_set(d->priv->domain_list_store, &iter, DOMAIN_COLUMN_REF, ref - 1, -1);
571                     break;
572                 }
573                 
574                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(d->priv->domain_list_store), &iter);
575             }
576             
577             break;
578         }
579             
580
581         case AVAHI_BROWSER_FAILURE: {
582             GtkWidget *m = gtk_message_dialog_new(GTK_WINDOW(d),
583                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
584                                                   GTK_MESSAGE_ERROR,
585                                                   GTK_BUTTONS_CLOSE,
586                                                   "Avahi domain browser failure: %s",
587                                                   avahi_strerror(avahi_client_errno(d->priv->client)));
588             gtk_dialog_run(GTK_DIALOG(m));
589             gtk_widget_destroy(m);
590
591             /* Fall through */
592         }
593
594         case AVAHI_BROWSER_ALL_FOR_NOW:
595             if (d->priv->domain_pulse_timeout > 0) {
596                 g_source_remove(d->priv->domain_pulse_timeout);
597                 d->priv->domain_pulse_timeout = 0;
598                 gtk_widget_hide(d->priv->domain_progress_bar);
599             }
600             break;
601
602         case AVAHI_BROWSER_CACHE_EXHAUSTED:
603             ;
604     }
605 }
606
607 static const gchar *get_domain_name(AuiServiceDialog *d) {
608     const gchar *domain;
609     
610     g_return_val_if_fail(d, NULL);
611     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
612     
613     if (d->priv->domain)
614         return d->priv->domain;
615
616     if (!(domain = avahi_client_get_domain_name(d->priv->client))) {
617         GtkWidget *m = gtk_message_dialog_new(GTK_WINDOW(d),
618                                               GTK_DIALOG_DESTROY_WITH_PARENT,
619                                               GTK_MESSAGE_ERROR,
620                                               GTK_BUTTONS_CLOSE,
621                                               "Failed to read Avahi domain : %s",
622                                               avahi_strerror(avahi_client_errno(d->priv->client)));
623         gtk_dialog_run(GTK_DIALOG(m));
624         gtk_widget_destroy(m);
625
626         return NULL;
627     }
628
629     return domain;
630 }
631
632 static gboolean start_callback(gpointer data) {
633     int error;
634     AuiServiceDialog *d = AUI_SERVICE_DIALOG(data);
635     gchar **st;
636     AvahiServiceBrowser **sb;
637     unsigned i;
638     const char *domain;
639
640     d->priv->start_idle = 0;
641     
642     if (!d->priv->browse_service_types || !*d->priv->browse_service_types) {
643         g_warning("Browse service type list is empty!");
644         return FALSE;
645     }
646
647     if (!d->priv->client) {
648         if (!(d->priv->client = avahi_client_new(avahi_glib_poll_get(d->priv->glib_poll), 0, client_callback, d, &error))) {
649             
650             GtkWidget *m = gtk_message_dialog_new(GTK_WINDOW(d),
651                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
652                                                   GTK_MESSAGE_ERROR,
653                                                   GTK_BUTTONS_CLOSE,
654                                                   "Failed to connect to Avahi server: %s",
655                                                   avahi_strerror(error));
656             gtk_dialog_run(GTK_DIALOG(m));
657             gtk_widget_destroy(m);
658             
659             gtk_dialog_response(GTK_DIALOG(d), GTK_RESPONSE_CANCEL);
660             return FALSE;
661         }
662     }
663
664     if (!(domain = get_domain_name(d))) {
665         gtk_dialog_response(GTK_DIALOG(d), GTK_RESPONSE_CANCEL);
666         return FALSE;
667     }
668
669     g_assert(domain);
670
671     if (avahi_domain_equal(domain, "local."))
672         gtk_label_set_markup(GTK_LABEL(d->priv->domain_label), "Browsing for services on <b>local network</b>:");
673     else {
674         gchar *t = g_strdup_printf("Browsing for services in domain <b>%s</b>:", domain);
675         gtk_label_set_markup(GTK_LABEL(d->priv->domain_label), t);
676         g_free(t);
677     }
678     
679     if (d->priv->browsers) {
680         for (sb = d->priv->browsers; *sb; sb++)
681             avahi_service_browser_free(*sb);
682
683         g_free(d->priv->browsers);
684         d->priv->browsers = NULL;
685     }
686
687     gtk_list_store_clear(GTK_LIST_STORE(d->priv->service_list_store));
688     d->priv->common_interface = AVAHI_IF_UNSPEC;
689     d->priv->common_protocol = AVAHI_PROTO_UNSPEC;
690
691     gtk_tree_view_column_set_visible(gtk_tree_view_get_column(GTK_TREE_VIEW(d->priv->service_tree_view), 0), FALSE);
692     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->priv->service_tree_view), gtk_tree_view_column_get_visible(gtk_tree_view_get_column(GTK_TREE_VIEW(d->priv->service_tree_view), 2)));
693     gtk_widget_show(d->priv->service_progress_bar);
694
695     if (d->priv->service_pulse_timeout <= 0)
696         d->priv->service_pulse_timeout = g_timeout_add(100, service_pulse_callback, d);
697
698     for (i = 0; d->priv->browse_service_types[i]; i++)
699         ;
700     g_assert(i > 0);
701
702     d->priv->browsers = g_new0(AvahiServiceBrowser*, i+1);
703     for (st = d->priv->browse_service_types, sb = d->priv->browsers; *st; st++, sb++) {
704
705         if (!(*sb = avahi_service_browser_new(d->priv->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, *st, d->priv->domain, 0, browse_callback, d))) {
706             GtkWidget *m = gtk_message_dialog_new(GTK_WINDOW(d),
707                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
708                                                   GTK_MESSAGE_ERROR,
709                                                   GTK_BUTTONS_CLOSE,
710                                                   "Failed to create browser for %s: %s",
711                                                   *st,
712                                                   avahi_strerror(avahi_client_errno(d->priv->client)));
713             gtk_dialog_run(GTK_DIALOG(m));
714             gtk_widget_destroy(m);
715             
716             gtk_dialog_response(GTK_DIALOG(d), GTK_RESPONSE_CANCEL);
717             return FALSE;
718
719         }
720     }
721
722     return FALSE;
723 }
724
725 static void aui_service_dialog_finalize(GObject *object) {
726     AuiServiceDialog *d = AUI_SERVICE_DIALOG(object);
727
728     if (d->priv->domain_pulse_timeout > 0)
729         g_source_remove(d->priv->domain_pulse_timeout);
730
731     if (d->priv->service_pulse_timeout > 0)
732         g_source_remove(d->priv->service_pulse_timeout);
733
734     if (d->priv->start_idle > 0)
735         g_source_remove(d->priv->start_idle);
736     
737     g_free(d->priv->host_name);
738     g_free(d->priv->domain);
739     g_free(d->priv->service_name);
740
741     avahi_string_list_free(d->priv->txt_data);
742     
743     g_strfreev(d->priv->browse_service_types);
744
745     if (d->priv->domain_browser)
746         avahi_domain_browser_free(d->priv->domain_browser);
747     
748     if (d->priv->resolver)
749         avahi_service_resolver_free(d->priv->resolver);
750     
751     if (d->priv->browsers) {
752         AvahiServiceBrowser **sb;
753
754         for (sb = d->priv->browsers; *sb; sb++)
755             avahi_service_browser_free(*sb);
756
757         g_free(d->priv->browsers);
758     }
759
760     if (d->priv->client)
761         avahi_client_free(d->priv->client);
762
763     if (d->priv->glib_poll)
764         avahi_glib_poll_free(d->priv->glib_poll);
765
766     g_free(d->priv);
767     d->priv = NULL;
768
769     G_OBJECT_CLASS(aui_service_dialog_parent_class)->finalize(object);
770 }
771
772 static void service_row_activated_callback(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) {
773     AuiServiceDialog *d = AUI_SERVICE_DIALOG(user_data);
774
775     gtk_dialog_response(GTK_DIALOG(d), GTK_RESPONSE_ACCEPT);
776 }
777
778 static void service_selection_changed_callback(GtkTreeSelection *selection, gpointer user_data) {
779     AuiServiceDialog *d = AUI_SERVICE_DIALOG(user_data);
780
781     gtk_dialog_set_response_sensitive(GTK_DIALOG(d), GTK_RESPONSE_ACCEPT, gtk_tree_selection_get_selected(selection, NULL, NULL));
782 }
783
784 static void response_callback(GtkDialog *dialog, gint response, gpointer user_data) {
785     AuiServiceDialog *d = AUI_SERVICE_DIALOG(user_data);
786
787     if (response == GTK_RESPONSE_ACCEPT &&
788         ((d->priv->resolve_service && !d->priv->resolve_service_done) ||
789          (d->priv->resolve_host_name && !d->priv->resolve_host_name_done))) {
790         
791         GtkTreeIter iter;
792         gint interface, protocol;
793         gchar *name, *type;
794         GdkCursor *cursor;
795
796         g_signal_stop_emission(dialog, g_signal_lookup("response", gtk_dialog_get_type()), 0);
797
798         if (d->priv->resolver)
799             return;
800
801         g_return_if_fail(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(d->priv->service_tree_view)), NULL, &iter));
802
803         gtk_tree_model_get(GTK_TREE_MODEL(d->priv->service_list_store), &iter,
804                            SERVICE_COLUMN_IFACE, &interface,
805                            SERVICE_COLUMN_PROTO, &protocol,
806                            SERVICE_COLUMN_NAME, &name,
807                            SERVICE_COLUMN_TYPE, &type, -1);
808
809         g_return_if_fail(d->priv->client);
810
811         gtk_widget_set_sensitive(GTK_WIDGET(dialog), FALSE);
812         cursor = gdk_cursor_new(GDK_WATCH);
813         gdk_window_set_cursor(GTK_WIDGET(dialog)->window, cursor);
814         gdk_cursor_unref(cursor);
815
816         if (!(d->priv->resolver = avahi_service_resolver_new(
817                       d->priv->client, interface, protocol, name, type, d->priv->domain,
818                       d->priv->address_family, !d->priv->resolve_host_name ? AVAHI_LOOKUP_NO_ADDRESS : 0, resolve_callback, d))) {
819
820             GtkWidget *m = gtk_message_dialog_new(GTK_WINDOW(d),
821                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
822                                                   GTK_MESSAGE_ERROR,
823                                                   GTK_BUTTONS_CLOSE,
824                                                   "Failed to create resolver for %s of type %s in domain %s: %s",
825                                                   name, type, d->priv->domain,
826                                                   avahi_strerror(avahi_client_errno(d->priv->client)));
827             gtk_dialog_run(GTK_DIALOG(m));
828             gtk_widget_destroy(m);
829             
830             gtk_dialog_response(GTK_DIALOG(d), GTK_RESPONSE_CANCEL);
831             return;
832         }
833     }
834 }
835
836 static gboolean is_valid_domain_suffix(const gchar *n) {
837     gchar label[AVAHI_LABEL_MAX];
838     
839     if (!avahi_is_valid_domain_name(n))
840         return FALSE;
841
842     if (!avahi_unescape_label(&n, label, sizeof(label)))
843         return FALSE;
844
845     /* At least one label */
846     
847     return !!label[0];
848 }
849
850 static void domain_row_activated_callback(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) {
851     AuiServiceDialog *d = AUI_SERVICE_DIALOG(user_data);
852
853     if (is_valid_domain_suffix(gtk_entry_get_text(GTK_ENTRY(d->priv->domain_entry))))
854         gtk_dialog_response(GTK_DIALOG(d->priv->domain_dialog), GTK_RESPONSE_ACCEPT);
855 }
856
857 static void domain_selection_changed_callback(GtkTreeSelection *selection, gpointer user_data) {
858     GtkTreeIter iter;
859     AuiServiceDialog *d = AUI_SERVICE_DIALOG(user_data);
860     gchar *name;
861
862     g_return_if_fail(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(d->priv->domain_tree_view)), NULL, &iter));
863
864     gtk_tree_model_get(GTK_TREE_MODEL(d->priv->domain_list_store), &iter,
865                        DOMAIN_COLUMN_NAME, &name, -1);
866
867     gtk_entry_set_text(GTK_ENTRY(d->priv->domain_entry), name);
868 }
869
870 static void domain_entry_changed_callback(GtkEditable *editable, gpointer user_data) {
871     AuiServiceDialog *d = AUI_SERVICE_DIALOG(user_data);
872
873     gtk_widget_set_sensitive(d->priv->domain_ok_button, is_valid_domain_suffix(gtk_entry_get_text(GTK_ENTRY(d->priv->domain_entry))));
874 }
875
876 static void domain_button_clicked(GtkButton *button, gpointer user_data) {
877     GtkWidget *vbox, *vbox2, *scrolled_window;
878     GtkTreeSelection *selection;
879     GtkCellRenderer *renderer;
880     GtkTreeViewColumn *column;
881     AuiServiceDialog *d = AUI_SERVICE_DIALOG(user_data);
882     AuiServiceDialogPrivate *p = d->priv;
883     const gchar *domain;
884     GtkTreeIter iter;
885
886     g_return_if_fail(!p->domain_dialog);
887     g_return_if_fail(!p->domain_browser);
888
889     if (!(domain = get_domain_name(d))) {
890         gtk_dialog_response(GTK_DIALOG(d), GTK_RESPONSE_CANCEL);
891         return;
892     }
893
894     if (!(p->domain_browser = avahi_domain_browser_new(p->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, AVAHI_DOMAIN_BROWSER_BROWSE, 0, domain_browse_callback, d))) {
895         GtkWidget *m = gtk_message_dialog_new(GTK_WINDOW(d),
896                                               GTK_DIALOG_DESTROY_WITH_PARENT,
897                                               GTK_MESSAGE_ERROR,
898                                               GTK_BUTTONS_CLOSE,
899                                               "Failed to create domain browser: %s",
900                                               avahi_strerror(avahi_client_errno(p->client)));
901         gtk_dialog_run(GTK_DIALOG(m));
902         gtk_widget_destroy(m);
903         
904         gtk_dialog_response(GTK_DIALOG(d), GTK_RESPONSE_CANCEL);
905         return;
906     }
907     
908     p->domain_dialog = gtk_dialog_new();
909     gtk_container_set_border_width(GTK_CONTAINER(p->domain_dialog), 5);
910     gtk_window_set_title(GTK_WINDOW(p->domain_dialog), "Change domain");
911     gtk_dialog_set_has_separator(GTK_DIALOG(p->domain_dialog), FALSE);
912     
913     vbox = gtk_vbox_new(FALSE, 8);
914     gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
915     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(p->domain_dialog)->vbox), vbox, TRUE, TRUE, 0);
916
917     p->domain_entry = gtk_entry_new_with_max_length(AVAHI_DOMAIN_NAME_MAX);
918     gtk_entry_set_text(GTK_ENTRY(p->domain_entry), domain);
919     gtk_entry_set_activates_default(GTK_ENTRY(p->domain_entry), TRUE);
920     g_signal_connect(p->domain_entry, "changed", G_CALLBACK(domain_entry_changed_callback), d);
921     gtk_box_pack_start(GTK_BOX(vbox), p->domain_entry, FALSE, FALSE, 0);
922     
923     vbox2 = gtk_vbox_new(FALSE, 8);
924     gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
925
926     scrolled_window = gtk_scrolled_window_new(NULL, NULL);
927     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
928     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_ETCHED_IN);
929     gtk_box_pack_start(GTK_BOX(vbox2), scrolled_window, TRUE, TRUE, 0);
930
931     p->domain_list_store = gtk_list_store_new(N_DOMAIN_COLUMNS, G_TYPE_STRING, G_TYPE_INT);
932
933     p->domain_tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(p->domain_list_store));
934     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(p->domain_tree_view), FALSE);
935     g_signal_connect(p->domain_tree_view, "row-activated", G_CALLBACK(domain_row_activated_callback), d);
936     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(p->domain_tree_view));
937     gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
938     g_signal_connect(selection, "changed", G_CALLBACK(domain_selection_changed_callback), d);
939
940     renderer = gtk_cell_renderer_text_new();
941     column = gtk_tree_view_column_new_with_attributes("Service Name", renderer, "text", DOMAIN_COLUMN_NAME, NULL);
942     gtk_tree_view_column_set_expand(column, TRUE);
943     gtk_tree_view_append_column(GTK_TREE_VIEW(p->domain_tree_view), column);
944
945     gtk_tree_view_set_search_column(GTK_TREE_VIEW(p->domain_tree_view), DOMAIN_COLUMN_NAME);
946     gtk_container_add(GTK_CONTAINER(scrolled_window), p->domain_tree_view);
947
948     p->domain_progress_bar = gtk_progress_bar_new();
949     gtk_progress_bar_set_text(GTK_PROGRESS_BAR(p->domain_progress_bar), "Browsing ...");
950     gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(p->domain_progress_bar), 0.1);
951     gtk_box_pack_end(GTK_BOX(vbox2), p->domain_progress_bar, FALSE, FALSE, 0);
952     
953     gtk_dialog_add_button(GTK_DIALOG(p->domain_dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
954     p->domain_ok_button = GTK_WIDGET(gtk_dialog_add_button(GTK_DIALOG(p->domain_dialog), GTK_STOCK_OK, GTK_RESPONSE_ACCEPT));
955     gtk_dialog_set_default_response(GTK_DIALOG(p->domain_dialog), GTK_RESPONSE_ACCEPT);
956     gtk_widget_set_sensitive(p->domain_ok_button, is_valid_domain_suffix(gtk_entry_get_text(GTK_ENTRY(p->domain_entry))));
957
958     gtk_widget_grab_default(p->domain_ok_button);
959     gtk_widget_grab_focus(p->domain_entry);
960
961     gtk_window_set_default_size(GTK_WINDOW(p->domain_dialog), 300, 300);
962     
963     gtk_widget_show_all(vbox);
964
965     gtk_list_store_append(p->domain_list_store, &iter);
966     gtk_list_store_set(p->domain_list_store, &iter, DOMAIN_COLUMN_NAME, "local", DOMAIN_COLUMN_REF, 1, -1);
967     domain_make_default_selection(d, "local", &iter);
968
969     p->domain_pulse_timeout = g_timeout_add(100, domain_pulse_callback, d);
970     
971     if (gtk_dialog_run(GTK_DIALOG(p->domain_dialog)) == GTK_RESPONSE_ACCEPT)
972         aui_service_dialog_set_domain(d, gtk_entry_get_text(GTK_ENTRY(p->domain_entry)));
973
974     gtk_widget_destroy(p->domain_dialog);
975     p->domain_dialog = NULL;
976
977     if (p->domain_pulse_timeout > 0) {
978         g_source_remove(p->domain_pulse_timeout);
979         p->domain_pulse_timeout = 0;
980     }
981
982     avahi_domain_browser_free(p->domain_browser);
983     p->domain_browser = NULL;
984 }
985
986 static void aui_service_dialog_init(AuiServiceDialog *d) {
987     GtkWidget *vbox, *vbox2, *scrolled_window;
988     GtkCellRenderer *renderer;
989     GtkTreeViewColumn *column;
990     GtkTreeSelection *selection;
991     AuiServiceDialogPrivate *p;
992
993     p = d->priv = g_new(AuiServiceDialogPrivate, 1);
994     
995     p->host_name = NULL;
996     p->domain = NULL;
997     p->service_name = NULL;
998     p->service_type = NULL;
999     p->txt_data = NULL;
1000     p->browse_service_types = NULL;
1001     memset(&p->address, 0, sizeof(p->address));
1002     p->port = 0;
1003     p->resolve_host_name = p->resolve_service = TRUE;
1004     p->resolve_host_name_done = p->resolve_service_done = FALSE;
1005     p->address_family = AVAHI_PROTO_UNSPEC;
1006
1007     p->glib_poll = NULL;
1008     p->client = NULL;
1009     p->browsers = NULL;
1010     p->resolver = NULL;
1011     p->domain_browser = NULL;
1012
1013     p->service_pulse_timeout = 0;
1014     p->domain_pulse_timeout = 0;
1015     p->start_idle = 0;
1016     p->common_interface = AVAHI_IF_UNSPEC;
1017     p->common_protocol = AVAHI_PROTO_UNSPEC;
1018
1019     p->domain_dialog = NULL;
1020     p->domain_entry = NULL;
1021     p->domain_tree_view = NULL;
1022     p->domain_progress_bar = NULL;
1023     p->domain_ok_button = NULL;
1024
1025     p->service_list_store = p->domain_list_store = NULL;
1026     
1027     gtk_widget_push_composite_child();
1028
1029     gtk_container_set_border_width(GTK_CONTAINER(d), 5);
1030
1031     vbox = gtk_vbox_new(FALSE, 8);
1032     gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1033     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(d)->vbox), vbox, TRUE, TRUE, 0);
1034
1035     p->domain_label = gtk_label_new("Initializing...");
1036     gtk_label_set_ellipsize(GTK_LABEL(p->domain_label), TRUE);
1037     gtk_misc_set_alignment(GTK_MISC(p->domain_label), 0, 0.5);
1038     gtk_box_pack_start(GTK_BOX(vbox), p->domain_label, FALSE, FALSE, 0);
1039     
1040
1041     vbox2 = gtk_vbox_new(FALSE, 8);
1042     gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
1043
1044     scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1045     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1046     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_ETCHED_IN);
1047     gtk_box_pack_start(GTK_BOX(vbox2), scrolled_window, TRUE, TRUE, 0);
1048
1049     p->service_list_store = gtk_list_store_new(N_SERVICE_COLUMNS, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
1050
1051     p->service_tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(p->service_list_store));
1052     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(p->service_tree_view), FALSE);
1053     g_signal_connect(p->service_tree_view, "row-activated", G_CALLBACK(service_row_activated_callback), d);
1054     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(p->service_tree_view));
1055     gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
1056     g_signal_connect(selection, "changed", G_CALLBACK(service_selection_changed_callback), d);
1057     
1058     renderer = gtk_cell_renderer_text_new();
1059     column = gtk_tree_view_column_new_with_attributes("Location", renderer, "text", SERVICE_COLUMN_PRETTY_IFACE, NULL);
1060     gtk_tree_view_column_set_visible(column, FALSE);
1061     gtk_tree_view_append_column(GTK_TREE_VIEW(p->service_tree_view), column);
1062     
1063     renderer = gtk_cell_renderer_text_new();
1064     column = gtk_tree_view_column_new_with_attributes("Name", renderer, "text", SERVICE_COLUMN_NAME, NULL);
1065     gtk_tree_view_column_set_expand(column, TRUE);
1066     gtk_tree_view_append_column(GTK_TREE_VIEW(p->service_tree_view), column);
1067
1068     renderer = gtk_cell_renderer_text_new();
1069     column = gtk_tree_view_column_new_with_attributes("Type", renderer, "text", SERVICE_COLUMN_PRETTY_TYPE, NULL);
1070     gtk_tree_view_column_set_visible(column, FALSE);
1071     gtk_tree_view_append_column(GTK_TREE_VIEW(p->service_tree_view), column);
1072
1073     gtk_tree_view_set_search_column(GTK_TREE_VIEW(p->service_tree_view), SERVICE_COLUMN_NAME);
1074     gtk_container_add(GTK_CONTAINER(scrolled_window), p->service_tree_view);
1075
1076     p->service_progress_bar = gtk_progress_bar_new();
1077     gtk_progress_bar_set_text(GTK_PROGRESS_BAR(p->service_progress_bar), "Browsing ...");
1078     gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(p->service_progress_bar), 0.1);
1079     gtk_box_pack_end(GTK_BOX(vbox2), p->service_progress_bar, FALSE, FALSE, 0);
1080
1081     p->domain_button = gtk_button_new_with_mnemonic("_Domain..."); 
1082     gtk_button_set_image(GTK_BUTTON(p->domain_button), gtk_image_new_from_stock(GTK_STOCK_NETWORK, GTK_ICON_SIZE_BUTTON)); 
1083     g_signal_connect(p->domain_button, "clicked", G_CALLBACK(domain_button_clicked), d);
1084     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(d)->action_area), p->domain_button, FALSE, TRUE, 0);
1085     gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(GTK_DIALOG(d)->action_area), p->domain_button, TRUE);
1086     gtk_widget_show(p->domain_button); 
1087     
1088     gtk_dialog_set_default_response(GTK_DIALOG(d), GTK_RESPONSE_ACCEPT);
1089     gtk_dialog_set_response_sensitive(GTK_DIALOG(d), GTK_RESPONSE_ACCEPT, FALSE);
1090
1091     gtk_widget_grab_focus(p->service_tree_view);
1092
1093     gtk_window_set_default_size(GTK_WINDOW(d), 400, 300);
1094     
1095     gtk_widget_show_all(vbox);
1096
1097     gtk_widget_pop_composite_child();
1098
1099     p->glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT);
1100
1101     p->service_pulse_timeout = g_timeout_add(100, service_pulse_callback, d);
1102     p->start_idle = g_idle_add(start_callback, d);
1103
1104     g_signal_connect(d, "response", G_CALLBACK(response_callback), d);
1105 }
1106
1107 static void restart_browsing(AuiServiceDialog *d) {
1108     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1109
1110     if (d->priv->start_idle <= 0)
1111         d->priv->start_idle = g_idle_add(start_callback, d);
1112 }
1113
1114 void aui_service_dialog_set_browse_service_types(AuiServiceDialog *d, const char *type, ...) {
1115     va_list ap, apcopy;
1116     const char *t;
1117     unsigned u;
1118     
1119     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1120     g_return_if_fail(type);
1121
1122     g_strfreev(d->priv->browse_service_types);
1123     
1124     va_copy(apcopy, ap);
1125     
1126     va_start(ap, type);
1127     for (u = 1; va_arg(ap, const char *); u++)
1128         ;
1129     va_end(ap);
1130
1131     d->priv->browse_service_types = g_new0(gchar*, u+1);
1132     d->priv->browse_service_types[0] = g_strdup(type);
1133     
1134     va_start(apcopy, type);
1135     for (u = 1; (t = va_arg(apcopy, const char*)); u++)
1136         d->priv->browse_service_types[u] = g_strdup(t);
1137     va_end(apcopy);
1138
1139     if (d->priv->browse_service_types[0] && d->priv->browse_service_types[1]) {
1140         /* Multiple service types, enable headers */
1141     
1142         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->priv->service_tree_view), TRUE);
1143         gtk_tree_view_column_set_visible(gtk_tree_view_get_column(GTK_TREE_VIEW(d->priv->service_tree_view), 2), TRUE);
1144     }
1145
1146     restart_browsing(d);
1147 }
1148
1149 void aui_service_dialog_set_browse_service_typesv(AuiServiceDialog *d, const char *const*types) {
1150     
1151     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1152     g_return_if_fail(types);
1153     g_return_if_fail(*types);
1154
1155     g_strfreev(d->priv->browse_service_types);
1156     d->priv->browse_service_types = g_strdupv((char**) types);
1157
1158     if (d->priv->browse_service_types[0] && d->priv->browse_service_types[1]) {
1159         /* Multiple service types, enable headers */
1160     
1161         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->priv->service_tree_view), TRUE);
1162         gtk_tree_view_column_set_visible(gtk_tree_view_get_column(GTK_TREE_VIEW(d->priv->service_tree_view), 2), TRUE);
1163     }
1164
1165     restart_browsing(d);
1166 }
1167
1168 const gchar*const* aui_service_dialog_get_browse_service_types(AuiServiceDialog *d) {
1169     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1170
1171     return (const char* const*) d->priv->browse_service_types;
1172 }
1173
1174 void aui_service_dialog_set_domain(AuiServiceDialog *d, const char *domain) {
1175     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1176     g_return_if_fail(!domain || is_valid_domain_suffix(domain));
1177
1178     g_free(d->priv->domain);
1179     d->priv->domain = domain ? avahi_normalize_name_strdup(domain) : NULL;
1180     
1181     restart_browsing(d);
1182 }
1183
1184 const char* aui_service_dialog_get_domain(AuiServiceDialog *d) {
1185     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1186     
1187     return d->priv->domain;
1188 }
1189
1190 void aui_service_dialog_set_service_name(AuiServiceDialog *d, const char *name) {
1191     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1192
1193     g_free(d->priv->service_name);
1194     d->priv->service_name = g_strdup(name);
1195 }
1196
1197 const char* aui_service_dialog_get_service_name(AuiServiceDialog *d) {
1198     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1199
1200     return d->priv->service_name;
1201 }
1202
1203 void aui_service_dialog_set_service_type(AuiServiceDialog *d, const char*stype) {
1204     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1205
1206     g_free(d->priv->service_type);
1207     d->priv->service_type = g_strdup(stype);
1208 }
1209
1210 const char* aui_service_dialog_get_service_type(AuiServiceDialog *d) {
1211     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1212
1213     return d->priv->service_type;
1214 }
1215
1216 const AvahiAddress* aui_service_dialog_get_address(AuiServiceDialog *d) {
1217     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1218     g_return_val_if_fail(d->priv->resolve_service_done && d->priv->resolve_host_name_done, NULL);
1219
1220     return &d->priv->address;
1221 }
1222
1223 guint16 aui_service_dialog_get_port(AuiServiceDialog *d) {
1224     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), 0);
1225     g_return_val_if_fail(d->priv->resolve_service_done, 0);
1226
1227     return d->priv->port;
1228 }
1229
1230 const char* aui_service_dialog_get_host_name(AuiServiceDialog *d) {
1231     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1232     g_return_val_if_fail(d->priv->resolve_service_done, NULL);
1233
1234     return d->priv->host_name;
1235 }
1236
1237 const AvahiStringList *aui_service_dialog_get_txt_data(AuiServiceDialog *d) {
1238     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1239     g_return_val_if_fail(d->priv->resolve_service_done, NULL);
1240
1241     return d->priv->txt_data;
1242 }
1243
1244 void aui_service_dialog_set_resolve_service(AuiServiceDialog *d, gboolean resolve) {
1245     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1246     
1247     d->priv->resolve_service = resolve;
1248 }
1249
1250 gboolean aui_service_dialog_get_resolve_service(AuiServiceDialog *d) {
1251     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), FALSE);
1252
1253     return d->priv->resolve_service;
1254 }
1255
1256 void aui_service_dialog_set_resolve_host_name(AuiServiceDialog *d, gboolean resolve) {
1257     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1258     
1259     d->priv->resolve_host_name = resolve;
1260 }
1261
1262 gboolean aui_service_dialog_get_resolve_host_name(AuiServiceDialog *d) {
1263     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), FALSE);
1264
1265     return d->priv->resolve_host_name;
1266 }
1267
1268 void aui_service_dialog_set_address_family(AuiServiceDialog *d, AvahiProtocol proto) {
1269     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1270     g_return_if_fail(proto == AVAHI_PROTO_UNSPEC || proto == AVAHI_PROTO_INET || proto == AVAHI_PROTO_INET6);
1271
1272     d->priv->address_family = proto;
1273 }
1274
1275 AvahiProtocol aui_service_dialog_get_address_family(AuiServiceDialog *d) {
1276     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), AVAHI_PROTO_UNSPEC);
1277
1278     return d->priv->address_family;
1279 }
1280
1281 static void aui_service_dialog_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
1282     AuiServiceDialog *d = AUI_SERVICE_DIALOG(object);
1283     
1284     switch (prop_id) {
1285         case PROP_BROWSE_SERVICE_TYPES:
1286             aui_service_dialog_set_browse_service_typesv(d, g_value_get_pointer(value));
1287             break;
1288
1289         case PROP_DOMAIN:
1290             aui_service_dialog_set_domain(d, g_value_get_string(value));
1291             break;
1292
1293         case PROP_SERVICE_TYPE:
1294             aui_service_dialog_set_service_type(d, g_value_get_string(value));
1295             break;
1296
1297         case PROP_SERVICE_NAME:
1298             aui_service_dialog_set_service_name(d, g_value_get_string(value));
1299             break;
1300             
1301         case PROP_RESOLVE_SERVICE:
1302             aui_service_dialog_set_resolve_service(d, g_value_get_boolean(value));
1303             break;
1304
1305         case PROP_RESOLVE_HOST_NAME:
1306             aui_service_dialog_set_resolve_host_name(d, g_value_get_boolean(value));
1307             break;
1308
1309         case PROP_ADDRESS_FAMILY:
1310             aui_service_dialog_set_address_family(d, g_value_get_int(value));
1311             break;
1312             
1313         default:
1314             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1315             break;
1316     }
1317 }
1318
1319 static void aui_service_dialog_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
1320     AuiServiceDialog *d = AUI_SERVICE_DIALOG(object);
1321     
1322     switch (prop_id) {
1323         case PROP_BROWSE_SERVICE_TYPES:
1324             g_value_set_pointer(value, (gpointer) aui_service_dialog_get_browse_service_types(d));
1325             break;
1326
1327         case PROP_DOMAIN:
1328             g_value_set_string(value, aui_service_dialog_get_domain(d));
1329             break;
1330
1331         case PROP_SERVICE_TYPE:
1332             g_value_set_string(value, aui_service_dialog_get_service_type(d));
1333             break;
1334
1335         case PROP_SERVICE_NAME:
1336             g_value_set_string(value, aui_service_dialog_get_service_name(d));
1337             break;
1338
1339         case PROP_ADDRESS:
1340             g_value_set_pointer(value, (gpointer) aui_service_dialog_get_address(d));
1341             break;
1342
1343         case PROP_PORT:
1344             g_value_set_uint(value, aui_service_dialog_get_port(d));
1345             break;
1346
1347         case PROP_HOST_NAME:
1348             g_value_set_string(value, aui_service_dialog_get_host_name(d));
1349             break;
1350                                
1351         case PROP_TXT_DATA:
1352             g_value_set_pointer(value, (gpointer) aui_service_dialog_get_txt_data(d));
1353             break;
1354             
1355         case PROP_RESOLVE_SERVICE:
1356             g_value_set_boolean(value, aui_service_dialog_get_resolve_service(d));
1357             break;
1358
1359         case PROP_RESOLVE_HOST_NAME:
1360             g_value_set_boolean(value, aui_service_dialog_get_resolve_host_name(d));
1361             break;
1362
1363         case PROP_ADDRESS_FAMILY:
1364             g_value_set_int(value, aui_service_dialog_get_address_family(d));
1365             break;
1366             
1367         default:
1368             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1369             break;
1370     }
1371 }
1372