]> git.meshlink.io Git - catta/blob - avahi-ui/avahi-ui.c
add i18n support
[catta] / avahi-ui / avahi-ui.c
1 /* $Id$ */
2
3 /***
4   This file is part of avahi.
5
6   avahi is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as
8   published by the Free Software Foundation; either version 2.1 of the
9   License, or (at your option) any later version.
10
11   avahi is distributed in the hope that it will be useful, but WITHOUT
12   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14   Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with avahi; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <string.h>
29 #include <stdarg.h>
30 #include <net/if.h>
31
32 #include <gtk/gtk.h>
33
34 #include <avahi-glib/glib-watch.h>
35 #include <avahi-client/client.h>
36 #include <avahi-client/lookup.h>
37 #include <avahi-common/error.h>
38 #include <avahi-common/address.h>
39 #include <avahi-common/domain.h>
40 #include <avahi-common/i18n.h>
41
42 #include "avahi-ui.h"
43
44 #if defined(HAVE_GDBM) || defined(HAVE_DBM)
45 #include "../avahi-utils/stdb.h"
46 #endif
47
48 /* todo: i18n, HIGify */
49
50 struct _AuiServiceDialogPrivate {
51     AvahiGLibPoll *glib_poll;
52     AvahiClient *client;
53     AvahiServiceBrowser **browsers;
54     AvahiServiceResolver *resolver;
55     AvahiDomainBrowser *domain_browser;
56
57     gchar **browse_service_types;
58     gchar *service_type;
59     gchar *domain;
60     gchar *service_name;
61     AvahiProtocol address_family;
62
63     AvahiAddress address;
64     gchar *host_name;
65     AvahiStringList *txt_data;
66     guint16 port;
67
68     gboolean resolve_service, resolve_service_done;
69     gboolean resolve_host_name, resolve_host_name_done;
70
71     GtkWidget *domain_label;
72     GtkWidget *domain_button;
73     GtkWidget *service_tree_view;
74     GtkWidget *service_progress_bar;
75
76     GtkListStore *service_list_store, *domain_list_store;
77     GHashTable *service_type_names;
78
79     guint service_pulse_timeout;
80     guint domain_pulse_timeout;
81     guint start_idle;
82
83     AvahiIfIndex common_interface;
84     AvahiProtocol common_protocol;
85
86     GtkWidget *domain_dialog;
87     GtkWidget *domain_entry;
88     GtkWidget *domain_tree_view;
89     GtkWidget *domain_progress_bar;
90     GtkWidget *domain_ok_button;
91
92     gint forward_response_id;
93 };
94
95 enum {
96     PROP_0,
97     PROP_BROWSE_SERVICE_TYPES,
98     PROP_DOMAIN,
99     PROP_SERVICE_TYPE,
100     PROP_SERVICE_NAME,
101     PROP_ADDRESS,
102     PROP_PORT,
103     PROP_HOST_NAME,
104     PROP_TXT_DATA,
105     PROP_RESOLVE_SERVICE,
106     PROP_RESOLVE_HOST_NAME,
107     PROP_ADDRESS_FAMILY
108 };
109
110 enum {
111     SERVICE_COLUMN_IFACE,
112     SERVICE_COLUMN_PROTO,
113     SERVICE_COLUMN_TYPE,
114     SERVICE_COLUMN_NAME,
115     SERVICE_COLUMN_PRETTY_IFACE,
116     SERVICE_COLUMN_PRETTY_TYPE,
117     N_SERVICE_COLUMNS
118 };
119
120 enum {
121     DOMAIN_COLUMN_NAME,
122     DOMAIN_COLUMN_REF,
123     N_DOMAIN_COLUMNS
124 };
125
126 static void aui_service_dialog_finalize(GObject *object);
127 static void aui_service_dialog_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
128 static void aui_service_dialog_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
129
130 static int get_default_response(GtkDialog *dlg) {
131     gint ret = GTK_RESPONSE_NONE;
132
133     if (GTK_WINDOW(dlg)->default_widget)
134         /* Use the response of the default widget, if possible */
135         ret = gtk_dialog_get_response_for_widget(dlg, GTK_WINDOW(dlg)->default_widget);
136
137     if (ret == GTK_RESPONSE_NONE) {
138         /* Fall back to finding the first positive response */
139         GList *children, *t;
140         gint bad = GTK_RESPONSE_NONE;
141
142         t = children = gtk_container_get_children(GTK_CONTAINER(dlg->action_area));
143
144         while (t) {
145             GtkWidget *child = t->data;
146
147             ret = gtk_dialog_get_response_for_widget(dlg, child);
148
149             if (ret == GTK_RESPONSE_ACCEPT ||
150                 ret == GTK_RESPONSE_OK ||
151                 ret == GTK_RESPONSE_YES ||
152                 ret == GTK_RESPONSE_APPLY)
153                 break;
154
155             if (ret != GTK_RESPONSE_NONE && bad == GTK_RESPONSE_NONE)
156                 bad = ret;
157
158             t = t->next;
159         }
160
161         g_list_free (children);
162
163         /* Fall back to finding the first negative response */
164         if (ret == GTK_RESPONSE_NONE)
165             ret = bad;
166     }
167
168     return ret;
169 }
170
171 G_DEFINE_TYPE(AuiServiceDialog, aui_service_dialog, GTK_TYPE_DIALOG)
172
173 static void aui_service_dialog_class_init(AuiServiceDialogClass *klass) {
174     GObjectClass *object_class;
175
176     avahi_init_i18n();
177
178     object_class = (GObjectClass*) klass;
179
180     object_class->finalize = aui_service_dialog_finalize;
181     object_class->set_property = aui_service_dialog_set_property;
182     object_class->get_property = aui_service_dialog_get_property;
183
184     g_object_class_install_property(
185             object_class,
186             PROP_BROWSE_SERVICE_TYPES,
187             g_param_spec_pointer("browse_service_types", "Browse Service Types", "A NULL terminated list of service types to browse for",
188                                 G_PARAM_READABLE | G_PARAM_WRITABLE));
189     g_object_class_install_property(
190             object_class,
191             PROP_DOMAIN,
192             g_param_spec_string("domain", "Domain", "The domain to browse in, or NULL for the default domain",
193                                 NULL,
194                                 G_PARAM_READABLE | G_PARAM_WRITABLE));
195     g_object_class_install_property(
196             object_class,
197             PROP_SERVICE_TYPE,
198             g_param_spec_string("service_type", "Service Type", "The service type of the selected service",
199                                 NULL,
200                                 G_PARAM_READABLE | G_PARAM_WRITABLE));
201     g_object_class_install_property(
202             object_class,
203             PROP_SERVICE_NAME,
204             g_param_spec_string("service_name", "Service Name", "The service name of the selected service",
205                                 NULL,
206                                 G_PARAM_READABLE | G_PARAM_WRITABLE));
207     g_object_class_install_property(
208             object_class,
209             PROP_ADDRESS,
210             g_param_spec_pointer("address", "Address", "The address of the resolved service",
211                                 G_PARAM_READABLE));
212     g_object_class_install_property(
213             object_class,
214             PROP_PORT,
215             g_param_spec_uint("port", "Port", "The IP port number of the resolved service",
216                              0, 0xFFFF, 0,
217                              G_PARAM_READABLE));
218     g_object_class_install_property(
219             object_class,
220             PROP_HOST_NAME,
221             g_param_spec_string("host_name", "Host Name", "The host name of the resolved service",
222                                 NULL,
223                                 G_PARAM_READABLE));
224     g_object_class_install_property(
225             object_class,
226             PROP_TXT_DATA,
227             g_param_spec_pointer("txt_data", "TXT Data", "The TXT data of the resolved service",
228                                 G_PARAM_READABLE));
229     g_object_class_install_property(
230             object_class,
231             PROP_RESOLVE_SERVICE,
232             g_param_spec_boolean("resolve_service", "Resolve service", "Resolve service",
233                                  TRUE,
234                                  G_PARAM_READABLE | G_PARAM_WRITABLE));
235     g_object_class_install_property(
236             object_class,
237             PROP_RESOLVE_HOST_NAME,
238             g_param_spec_boolean("resolve_host_name", "Resolve service host name", "Resolve service host name",
239                                  TRUE,
240                                  G_PARAM_READABLE | G_PARAM_WRITABLE));
241     g_object_class_install_property(
242             object_class,
243             PROP_ADDRESS_FAMILY,
244             g_param_spec_int("address_family", "Address family", "The address family for host name resolution",
245                              AVAHI_PROTO_UNSPEC, AVAHI_PROTO_INET6, AVAHI_PROTO_UNSPEC,
246                              G_PARAM_READABLE | G_PARAM_WRITABLE));
247 }
248
249
250 GtkWidget *aui_service_dialog_new_valist(
251         const gchar *title,
252         GtkWindow *parent,
253         const gchar *first_button_text,
254         va_list varargs) {
255
256     const gchar *button_text;
257     gint dr;
258
259     GtkWidget *w = GTK_WIDGET(g_object_new(
260                                       AUI_TYPE_SERVICE_DIALOG,
261                                       "has-separator", FALSE,
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
288 GtkWidget* 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
304 static 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
311 static 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
318 static 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
335 static 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
400 static 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
540 static 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
559 static 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
670 static 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
695 static 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
788 static 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
842 static void service_row_activated_callback(GtkTreeView *tree_view G_GNUC_UNUSED, GtkTreePath *path G_GNUC_UNUSED, GtkTreeViewColumn *column G_GNUC_UNUSED, gpointer user_data) {
843     AuiServiceDialog *d = AUI_SERVICE_DIALOG(user_data);
844
845     gtk_dialog_response(GTK_DIALOG(d), get_default_response(GTK_DIALOG(d)));
846 }
847
848 static 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
859 static 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(dialog)->window, cursor);
893         gdk_cursor_unref(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
915 static 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
929 static void domain_row_activated_callback(GtkTreeView *tree_view G_GNUC_UNUSED, GtkTreePath *path G_GNUC_UNUSED, GtkTreeViewColumn *column G_GNUC_UNUSED, gpointer user_data) {
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
936 static 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
949 static 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
955 static 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     gtk_dialog_set_has_separator(GTK_DIALOG(p->domain_dialog), FALSE);
991
992     vbox = gtk_vbox_new(FALSE, 8);
993     gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
994     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(p->domain_dialog)->vbox), vbox, TRUE, TRUE, 0);
995
996     p->domain_entry = gtk_entry_new();
997     gtk_entry_set_max_length(GTK_ENTRY(p->domain_entry), AVAHI_DOMAIN_NAME_MAX);
998     gtk_entry_set_text(GTK_ENTRY(p->domain_entry), domain);
999     gtk_entry_set_activates_default(GTK_ENTRY(p->domain_entry), TRUE);
1000     g_signal_connect(p->domain_entry, "changed", G_CALLBACK(domain_entry_changed_callback), d);
1001     gtk_box_pack_start(GTK_BOX(vbox), p->domain_entry, FALSE, FALSE, 0);
1002
1003     vbox2 = gtk_vbox_new(FALSE, 8);
1004     gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
1005
1006     scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1007     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1008     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_ETCHED_IN);
1009     gtk_box_pack_start(GTK_BOX(vbox2), scrolled_window, TRUE, TRUE, 0);
1010
1011     p->domain_list_store = gtk_list_store_new(N_DOMAIN_COLUMNS, G_TYPE_STRING, G_TYPE_INT);
1012
1013     p->domain_tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(p->domain_list_store));
1014     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(p->domain_tree_view), FALSE);
1015     g_signal_connect(p->domain_tree_view, "row-activated", G_CALLBACK(domain_row_activated_callback), d);
1016     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(p->domain_tree_view));
1017     gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
1018     g_signal_connect(selection, "changed", G_CALLBACK(domain_selection_changed_callback), d);
1019
1020     renderer = gtk_cell_renderer_text_new();
1021     column = gtk_tree_view_column_new_with_attributes(_("Service Name"), renderer, "text", DOMAIN_COLUMN_NAME, NULL);
1022     gtk_tree_view_column_set_expand(column, TRUE);
1023     gtk_tree_view_append_column(GTK_TREE_VIEW(p->domain_tree_view), column);
1024
1025     gtk_tree_view_set_search_column(GTK_TREE_VIEW(p->domain_tree_view), DOMAIN_COLUMN_NAME);
1026     gtk_container_add(GTK_CONTAINER(scrolled_window), p->domain_tree_view);
1027
1028     p->domain_progress_bar = gtk_progress_bar_new();
1029     gtk_progress_bar_set_text(GTK_PROGRESS_BAR(p->domain_progress_bar), _("Browsing ..."));
1030     gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(p->domain_progress_bar), 0.1);
1031     gtk_box_pack_end(GTK_BOX(vbox2), p->domain_progress_bar, FALSE, FALSE, 0);
1032
1033     gtk_dialog_add_button(GTK_DIALOG(p->domain_dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1034     p->domain_ok_button = GTK_WIDGET(gtk_dialog_add_button(GTK_DIALOG(p->domain_dialog), GTK_STOCK_OK, GTK_RESPONSE_ACCEPT));
1035     gtk_dialog_set_default_response(GTK_DIALOG(p->domain_dialog), GTK_RESPONSE_ACCEPT);
1036     gtk_widget_set_sensitive(p->domain_ok_button, is_valid_domain_suffix(gtk_entry_get_text(GTK_ENTRY(p->domain_entry))));
1037
1038     gtk_widget_grab_default(p->domain_ok_button);
1039     gtk_widget_grab_focus(p->domain_entry);
1040
1041     gtk_window_set_default_size(GTK_WINDOW(p->domain_dialog), 300, 300);
1042
1043     gtk_widget_show_all(vbox);
1044
1045     gtk_list_store_append(p->domain_list_store, &iter);
1046     gtk_list_store_set(p->domain_list_store, &iter, DOMAIN_COLUMN_NAME, "local", DOMAIN_COLUMN_REF, 1, -1);
1047     domain_make_default_selection(d, "local", &iter);
1048
1049     p->domain_pulse_timeout = g_timeout_add(100, domain_pulse_callback, d);
1050
1051     if (gtk_dialog_run(GTK_DIALOG(p->domain_dialog)) == GTK_RESPONSE_ACCEPT)
1052         aui_service_dialog_set_domain(d, gtk_entry_get_text(GTK_ENTRY(p->domain_entry)));
1053
1054     gtk_widget_destroy(p->domain_dialog);
1055     p->domain_dialog = NULL;
1056
1057     if (p->domain_pulse_timeout > 0) {
1058         g_source_remove(p->domain_pulse_timeout);
1059         p->domain_pulse_timeout = 0;
1060     }
1061
1062     avahi_domain_browser_free(p->domain_browser);
1063     p->domain_browser = NULL;
1064 }
1065
1066 static void aui_service_dialog_init(AuiServiceDialog *d) {
1067     GtkWidget *vbox, *vbox2, *scrolled_window;
1068     GtkCellRenderer *renderer;
1069     GtkTreeViewColumn *column;
1070     GtkTreeSelection *selection;
1071     AuiServiceDialogPrivate *p;
1072
1073     p = d->priv = g_new(AuiServiceDialogPrivate, 1);
1074
1075     p->host_name = NULL;
1076     p->domain = NULL;
1077     p->service_name = NULL;
1078     p->service_type = NULL;
1079     p->txt_data = NULL;
1080     p->browse_service_types = NULL;
1081     memset(&p->address, 0, sizeof(p->address));
1082     p->port = 0;
1083     p->resolve_host_name = p->resolve_service = TRUE;
1084     p->resolve_host_name_done = p->resolve_service_done = FALSE;
1085     p->address_family = AVAHI_PROTO_UNSPEC;
1086
1087     p->glib_poll = NULL;
1088     p->client = NULL;
1089     p->browsers = NULL;
1090     p->resolver = NULL;
1091     p->domain_browser = NULL;
1092
1093     p->service_pulse_timeout = 0;
1094     p->domain_pulse_timeout = 0;
1095     p->start_idle = 0;
1096     p->common_interface = AVAHI_IF_UNSPEC;
1097     p->common_protocol = AVAHI_PROTO_UNSPEC;
1098
1099     p->domain_dialog = NULL;
1100     p->domain_entry = NULL;
1101     p->domain_tree_view = NULL;
1102     p->domain_progress_bar = NULL;
1103     p->domain_ok_button = NULL;
1104
1105     p->forward_response_id = GTK_RESPONSE_NONE;
1106
1107     p->service_list_store = p->domain_list_store = NULL;
1108     p->service_type_names = NULL;
1109
1110     gtk_widget_push_composite_child();
1111
1112     gtk_container_set_border_width(GTK_CONTAINER(d), 5);
1113
1114     vbox = gtk_vbox_new(FALSE, 8);
1115     gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1116     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(d)->vbox), vbox, TRUE, TRUE, 0);
1117
1118     p->domain_label = gtk_label_new(_("Initializing..."));
1119     gtk_label_set_ellipsize(GTK_LABEL(p->domain_label), TRUE);
1120     gtk_misc_set_alignment(GTK_MISC(p->domain_label), 0, 0.5);
1121     gtk_box_pack_start(GTK_BOX(vbox), p->domain_label, FALSE, FALSE, 0);
1122
1123
1124     vbox2 = gtk_vbox_new(FALSE, 8);
1125     gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
1126
1127     scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1128     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1129     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_ETCHED_IN);
1130     gtk_box_pack_start(GTK_BOX(vbox2), scrolled_window, TRUE, TRUE, 0);
1131
1132     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);
1133
1134     p->service_tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(p->service_list_store));
1135     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(p->service_tree_view), FALSE);
1136     g_signal_connect(p->service_tree_view, "row-activated", G_CALLBACK(service_row_activated_callback), d);
1137     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(p->service_tree_view));
1138     gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
1139     g_signal_connect(selection, "changed", G_CALLBACK(service_selection_changed_callback), d);
1140
1141     renderer = gtk_cell_renderer_text_new();
1142     column = gtk_tree_view_column_new_with_attributes("Location", renderer, "text", SERVICE_COLUMN_PRETTY_IFACE, NULL);
1143     gtk_tree_view_column_set_visible(column, FALSE);
1144     gtk_tree_view_append_column(GTK_TREE_VIEW(p->service_tree_view), column);
1145
1146     renderer = gtk_cell_renderer_text_new();
1147     column = gtk_tree_view_column_new_with_attributes("Name", renderer, "text", SERVICE_COLUMN_NAME, NULL);
1148     gtk_tree_view_column_set_expand(column, TRUE);
1149     gtk_tree_view_append_column(GTK_TREE_VIEW(p->service_tree_view), column);
1150
1151     renderer = gtk_cell_renderer_text_new();
1152     column = gtk_tree_view_column_new_with_attributes("Type", renderer, "text", SERVICE_COLUMN_PRETTY_TYPE, NULL);
1153     gtk_tree_view_column_set_visible(column, FALSE);
1154     gtk_tree_view_append_column(GTK_TREE_VIEW(p->service_tree_view), column);
1155
1156     gtk_tree_view_set_search_column(GTK_TREE_VIEW(p->service_tree_view), SERVICE_COLUMN_NAME);
1157     gtk_container_add(GTK_CONTAINER(scrolled_window), p->service_tree_view);
1158
1159     p->service_progress_bar = gtk_progress_bar_new();
1160     gtk_progress_bar_set_text(GTK_PROGRESS_BAR(p->service_progress_bar), _("Browsing ..."));
1161     gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(p->service_progress_bar), 0.1);
1162     gtk_box_pack_end(GTK_BOX(vbox2), p->service_progress_bar, FALSE, FALSE, 0);
1163
1164     p->domain_button = gtk_button_new_with_mnemonic(_("_Domain..."));
1165     gtk_button_set_image(GTK_BUTTON(p->domain_button), gtk_image_new_from_stock(GTK_STOCK_NETWORK, GTK_ICON_SIZE_BUTTON));
1166     g_signal_connect(p->domain_button, "clicked", G_CALLBACK(domain_button_clicked), d);
1167     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(d)->action_area), p->domain_button, FALSE, TRUE, 0);
1168     gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(GTK_DIALOG(d)->action_area), p->domain_button, TRUE);
1169     gtk_widget_show(p->domain_button);
1170
1171     gtk_dialog_set_default_response(GTK_DIALOG(d), GTK_RESPONSE_ACCEPT);
1172
1173     gtk_widget_grab_focus(p->service_tree_view);
1174
1175     gtk_window_set_default_size(GTK_WINDOW(d), 400, 300);
1176
1177     gtk_widget_show_all(vbox);
1178
1179     gtk_widget_pop_composite_child();
1180
1181     p->glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT);
1182
1183     p->service_pulse_timeout = g_timeout_add(100, service_pulse_callback, d);
1184     p->start_idle = g_idle_add(start_callback, d);
1185
1186     g_signal_connect(d, "response", G_CALLBACK(response_callback), d);
1187 }
1188
1189 static void restart_browsing(AuiServiceDialog *d) {
1190     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1191
1192     if (d->priv->start_idle <= 0)
1193         d->priv->start_idle = g_idle_add(start_callback, d);
1194 }
1195
1196 void aui_service_dialog_set_browse_service_types(AuiServiceDialog *d, const char *type, ...) {
1197     va_list ap;
1198     const char *t;
1199     unsigned u;
1200
1201     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1202     g_return_if_fail(type);
1203
1204     g_strfreev(d->priv->browse_service_types);
1205
1206     va_start(ap, type);
1207     for (u = 1; va_arg(ap, const char *); u++)
1208         ;
1209     va_end(ap);
1210
1211     d->priv->browse_service_types = g_new0(gchar*, u+1);
1212     d->priv->browse_service_types[0] = g_strdup(type);
1213
1214     va_start(ap, type);
1215     for (u = 1; (t = va_arg(ap, const char*)); u++)
1216         d->priv->browse_service_types[u] = g_strdup(t);
1217     va_end(ap);
1218
1219     if (d->priv->browse_service_types[0] && d->priv->browse_service_types[1]) {
1220         /* Multiple service types, show type-column */
1221         gtk_tree_view_column_set_visible(gtk_tree_view_get_column(GTK_TREE_VIEW(d->priv->service_tree_view), 2), TRUE);
1222     }
1223
1224     restart_browsing(d);
1225 }
1226
1227 void aui_service_dialog_set_browse_service_typesv(AuiServiceDialog *d, const char *const*types) {
1228
1229     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1230     g_return_if_fail(types);
1231     g_return_if_fail(*types);
1232
1233     g_strfreev(d->priv->browse_service_types);
1234     d->priv->browse_service_types = g_strdupv((char**) types);
1235
1236     if (d->priv->browse_service_types[0] && d->priv->browse_service_types[1]) {
1237         /* Multiple service types, show type-column */
1238         gtk_tree_view_column_set_visible(gtk_tree_view_get_column(GTK_TREE_VIEW(d->priv->service_tree_view), 2), TRUE);
1239     }
1240
1241     restart_browsing(d);
1242 }
1243
1244 const gchar*const* aui_service_dialog_get_browse_service_types(AuiServiceDialog *d) {
1245     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1246
1247     return (const char* const*) d->priv->browse_service_types;
1248 }
1249
1250 void aui_service_dialog_set_service_type_name(AuiServiceDialog *d, const gchar *type, const gchar *name) {
1251     GtkTreeModel *m = NULL;
1252     GtkTreeIter iter;
1253
1254     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1255     g_return_if_fail(NULL != type);
1256     g_return_if_fail(NULL != name);
1257
1258     if (NULL == d->priv->service_type_names)
1259         d->priv->service_type_names = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1260
1261     g_hash_table_insert(d->priv->service_type_names, g_strdup(type), g_strdup(name));
1262
1263     if (d->priv->service_list_store)
1264         m = GTK_TREE_MODEL(d->priv->service_list_store);
1265
1266     if (m && gtk_tree_model_get_iter_first(m, &iter)) {
1267         do {
1268             char *stored_type = NULL;
1269
1270             gtk_tree_model_get(m, &iter, SERVICE_COLUMN_TYPE, &stored_type, -1);
1271
1272             if (stored_type && g_str_equal(stored_type, type))
1273                 gtk_list_store_set(d->priv->service_list_store, &iter, SERVICE_COLUMN_PRETTY_TYPE, name, -1);
1274         } while (gtk_tree_model_iter_next(m, &iter));
1275     }
1276 }
1277
1278 void aui_service_dialog_set_domain(AuiServiceDialog *d, const char *domain) {
1279     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1280     g_return_if_fail(!domain || is_valid_domain_suffix(domain));
1281
1282     g_free(d->priv->domain);
1283     d->priv->domain = domain ? avahi_normalize_name_strdup(domain) : NULL;
1284
1285     restart_browsing(d);
1286 }
1287
1288 const char* aui_service_dialog_get_domain(AuiServiceDialog *d) {
1289     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1290
1291     return d->priv->domain;
1292 }
1293
1294 void aui_service_dialog_set_service_name(AuiServiceDialog *d, const char *name) {
1295     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1296
1297     g_free(d->priv->service_name);
1298     d->priv->service_name = g_strdup(name);
1299 }
1300
1301 const char* aui_service_dialog_get_service_name(AuiServiceDialog *d) {
1302     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1303
1304     return d->priv->service_name;
1305 }
1306
1307 void aui_service_dialog_set_service_type(AuiServiceDialog *d, const char*stype) {
1308     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1309
1310     g_free(d->priv->service_type);
1311     d->priv->service_type = g_strdup(stype);
1312 }
1313
1314 const char* aui_service_dialog_get_service_type(AuiServiceDialog *d) {
1315     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1316
1317     return d->priv->service_type;
1318 }
1319
1320 const AvahiAddress* aui_service_dialog_get_address(AuiServiceDialog *d) {
1321     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1322     g_return_val_if_fail(d->priv->resolve_service_done && d->priv->resolve_host_name_done, NULL);
1323
1324     return &d->priv->address;
1325 }
1326
1327 guint16 aui_service_dialog_get_port(AuiServiceDialog *d) {
1328     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), 0);
1329     g_return_val_if_fail(d->priv->resolve_service_done, 0);
1330
1331     return d->priv->port;
1332 }
1333
1334 const char* aui_service_dialog_get_host_name(AuiServiceDialog *d) {
1335     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1336     g_return_val_if_fail(d->priv->resolve_service_done, NULL);
1337
1338     return d->priv->host_name;
1339 }
1340
1341 const AvahiStringList *aui_service_dialog_get_txt_data(AuiServiceDialog *d) {
1342     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), NULL);
1343     g_return_val_if_fail(d->priv->resolve_service_done, NULL);
1344
1345     return d->priv->txt_data;
1346 }
1347
1348 void aui_service_dialog_set_resolve_service(AuiServiceDialog *d, gboolean resolve) {
1349     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1350
1351     d->priv->resolve_service = resolve;
1352 }
1353
1354 gboolean aui_service_dialog_get_resolve_service(AuiServiceDialog *d) {
1355     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), FALSE);
1356
1357     return d->priv->resolve_service;
1358 }
1359
1360 void aui_service_dialog_set_resolve_host_name(AuiServiceDialog *d, gboolean resolve) {
1361     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1362
1363     d->priv->resolve_host_name = resolve;
1364 }
1365
1366 gboolean aui_service_dialog_get_resolve_host_name(AuiServiceDialog *d) {
1367     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), FALSE);
1368
1369     return d->priv->resolve_host_name;
1370 }
1371
1372 void aui_service_dialog_set_address_family(AuiServiceDialog *d, AvahiProtocol proto) {
1373     g_return_if_fail(AUI_IS_SERVICE_DIALOG(d));
1374     g_return_if_fail(proto == AVAHI_PROTO_UNSPEC || proto == AVAHI_PROTO_INET || proto == AVAHI_PROTO_INET6);
1375
1376     d->priv->address_family = proto;
1377 }
1378
1379 AvahiProtocol aui_service_dialog_get_address_family(AuiServiceDialog *d) {
1380     g_return_val_if_fail(AUI_IS_SERVICE_DIALOG(d), AVAHI_PROTO_UNSPEC);
1381
1382     return d->priv->address_family;
1383 }
1384
1385 static void aui_service_dialog_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
1386     AuiServiceDialog *d = AUI_SERVICE_DIALOG(object);
1387
1388     switch (prop_id) {
1389         case PROP_BROWSE_SERVICE_TYPES:
1390             aui_service_dialog_set_browse_service_typesv(d, g_value_get_pointer(value));
1391             break;
1392
1393         case PROP_DOMAIN:
1394             aui_service_dialog_set_domain(d, g_value_get_string(value));
1395             break;
1396
1397         case PROP_SERVICE_TYPE:
1398             aui_service_dialog_set_service_type(d, g_value_get_string(value));
1399             break;
1400
1401         case PROP_SERVICE_NAME:
1402             aui_service_dialog_set_service_name(d, g_value_get_string(value));
1403             break;
1404
1405         case PROP_RESOLVE_SERVICE:
1406             aui_service_dialog_set_resolve_service(d, g_value_get_boolean(value));
1407             break;
1408
1409         case PROP_RESOLVE_HOST_NAME:
1410             aui_service_dialog_set_resolve_host_name(d, g_value_get_boolean(value));
1411             break;
1412
1413         case PROP_ADDRESS_FAMILY:
1414             aui_service_dialog_set_address_family(d, g_value_get_int(value));
1415             break;
1416
1417         default:
1418             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1419             break;
1420     }
1421 }
1422
1423 static void aui_service_dialog_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
1424     AuiServiceDialog *d = AUI_SERVICE_DIALOG(object);
1425
1426     switch (prop_id) {
1427         case PROP_BROWSE_SERVICE_TYPES:
1428             g_value_set_pointer(value, (gpointer) aui_service_dialog_get_browse_service_types(d));
1429             break;
1430
1431         case PROP_DOMAIN:
1432             g_value_set_string(value, aui_service_dialog_get_domain(d));
1433             break;
1434
1435         case PROP_SERVICE_TYPE:
1436             g_value_set_string(value, aui_service_dialog_get_service_type(d));
1437             break;
1438
1439         case PROP_SERVICE_NAME:
1440             g_value_set_string(value, aui_service_dialog_get_service_name(d));
1441             break;
1442
1443         case PROP_ADDRESS:
1444             g_value_set_pointer(value, (gpointer) aui_service_dialog_get_address(d));
1445             break;
1446
1447         case PROP_PORT:
1448             g_value_set_uint(value, aui_service_dialog_get_port(d));
1449             break;
1450
1451         case PROP_HOST_NAME:
1452             g_value_set_string(value, aui_service_dialog_get_host_name(d));
1453             break;
1454
1455         case PROP_TXT_DATA:
1456             g_value_set_pointer(value, (gpointer) aui_service_dialog_get_txt_data(d));
1457             break;
1458
1459         case PROP_RESOLVE_SERVICE:
1460             g_value_set_boolean(value, aui_service_dialog_get_resolve_service(d));
1461             break;
1462
1463         case PROP_RESOLVE_HOST_NAME:
1464             g_value_set_boolean(value, aui_service_dialog_get_resolve_host_name(d));
1465             break;
1466
1467         case PROP_ADDRESS_FAMILY:
1468             g_value_set_int(value, aui_service_dialog_get_address_family(d));
1469             break;
1470
1471         default:
1472             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1473             break;
1474     }
1475 }