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