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