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