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