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