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