Modify version string to post-release version 11.8.17
[gmpc-avahi.git] / src / plugin.c
blobd3cef5c4ea57a51d25df270b8c8c5f66aa82e270
1 /* gmpc-avahi (GMPC plugin)
2 * Copyright (C) 2007-2009 Qball Cow <qball@sarine.nl>
3 * Copyright (C) 2007 Jim Ramsay <i.am@jimramsay.com>
4 * Project homepage: http://gmpcwiki.sarine.nl/
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include <stdio.h>
21 #include <string.h>
22 #include <config.h>
23 #include <glib.h>
24 #include <glib/gi18n-lib.h>
25 #include <glib/gstdio.h>
26 #include <gtk/gtk.h>
27 #include <libmpd/debug_printf.h>
28 #include <gmpc/plugin.h>
29 #include <gmpc/gmpc-profiles.h>
30 #include <libmpd/debug_printf.h>
32 #include <avahi-client/client.h>
33 #include <avahi-client/lookup.h>
35 #include <avahi-glib/glib-watch.h>
36 #include <avahi-glib/glib-malloc.h>
37 #include <avahi-common/domain.h>
38 #include <avahi-common/error.h>
40 #define SERVICE_TYPE "_mpd._tcp"
42 static void avahi_domain_changed(void);
43 static const char* avahi_get_default_domain(void);
44 static void avahi_add_service( const char *name, const char *host, int port );
45 static void avahi_del_service( const char *name );
46 static const char* avahi_get_browse_domain(void);
48 /**** This section is for the gmpc plugin part ****/
50 static void avahi_init(void);
51 static void avahi_init_real();
52 static void avahi_destroy(void);
54 static void avahi_pref_construct(GtkWidget *container);
55 static void avahi_pref_destroy(GtkWidget *container);
57 static void avahi_set_enabled(int enabled);
58 static int avahi_get_enabled(void);
60 static void avahi_update_profiles_menu(void);
62 static GtkWidget *pref_vbox;
63 gmpcPrefPlugin avahi_prefs = {
64 .construct = avahi_pref_construct,
65 .destroy = avahi_pref_destroy
68 int plugin_api_version = PLUGIN_API_VERSION;
70 static const gchar *avahi_get_translation_domain(void)
72 return GETTEXT_PACKAGE;
74 gmpcPlugin plugin = {
75 .name = "Avahi Zeroconf",
76 .version = {PLUGIN_MAJOR_VERSION, PLUGIN_MINOR_VERSION, PLUGIN_MICRO_VERSION},
77 .plugin_type = GMPC_PLUGIN_DUMMY,
78 .init = avahi_init_real,
79 .destroy = avahi_destroy,
80 .pref = &avahi_prefs,
81 .get_enabled = avahi_get_enabled,
82 .set_enabled = avahi_set_enabled,
84 .get_translation_domain = avahi_get_translation_domain
87 /**
88 * Get/Set enabled
90 static int avahi_get_enabled()
92 return cfg_get_single_value_as_int_with_default(config, "avahi-profiles", "enable", TRUE);
94 static void avahi_set_enabled(int enabled)
96 /* Get old state */
97 gboolean old = avahi_get_enabled();
98 /* Set new state */
99 cfg_set_single_value_as_int(config, "avahi-profiles", "enable", enabled);
100 /* if old enabled, new disabled, destroy avahi */
101 if(old && !enabled) {
102 avahi_destroy();
104 /* if old disabled new enabled, start avahi */
105 if(!old && enabled) {
106 avahi_init();
111 static void avahi_add_service( const char *name, const char *host, int port )
113 g_debug("Avahi service \"%s\" (%s:%i) added", name,host,port);
114 if(gmpc_profiles_has_profile(gmpc_profiles, name))
116 if(g_utf8_collate(gmpc_profiles_get_hostname(gmpc_profiles,name), host))
118 g_debug("Avahi service \"%s\" hostname update %s -> %s", name,
119 gmpc_profiles_get_hostname(gmpc_profiles,name),host);
120 gmpc_profiles_set_hostname(gmpc_profiles, name,host);
122 if(gmpc_profiles_get_port(gmpc_profiles,name) != port)
124 g_debug("Avahi service \"%s\" port update %i -> %i", name,gmpc_profiles_get_port(gmpc_profiles, name),port);
125 gmpc_profiles_set_port(gmpc_profiles, name,port);
128 else
130 /* create new */
131 gchar *id = gmpc_profiles_create_new_item_with_name(gmpc_profiles, name,name);
133 gmpc_profiles_set_hostname(gmpc_profiles, id,host);
134 gmpc_profiles_set_port(gmpc_profiles, id,port);
135 g_debug("Avahi service \"%s\" (%s:%i) created: id %s", name,host,port, id);
139 static void avahi_del_service( const char *name )
141 g_debug("Avahi service \"%s\" removed", name);
142 if(cfg_get_single_value_as_int_with_default(config, "avahi-profiles", "delete-on-disappear", FALSE))
144 gmpc_profiles_remove_item(gmpc_profiles, name);
149 static void avahi_del_all_services( void )
155 * Preferences
157 static const char* avahi_get_browse_domain(void)
159 static char value[128];
160 gchar *def = (gchar *)(avahi_get_default_domain());
161 if( !def )
162 def = "local";
163 char* tmpval = cfg_get_single_value_as_string_with_default(config, "avahi-profiles", "domain", def);
164 strncpy( value, tmpval, 128 );
165 value[127] = '\0';
166 cfg_free_string( tmpval );
167 return value;
170 static void avahi_profiles_domain_applied(GtkWidget *button, GtkWidget *entry)
172 const char *str = gtk_entry_get_text(GTK_ENTRY(entry));
173 if(str && strcmp( str, avahi_get_browse_domain() ) != 0)
175 if( avahi_is_valid_domain_name( str ) ) {
176 cfg_set_single_value_as_string(config, "avahi-profiles", "domain",(char *)str);
177 debug_printf(DEBUG_INFO, "Searching domain '%s'\n", str );
178 // Start browsing the new domain
179 avahi_domain_changed();
180 } else {
181 // TODO: Popup an error and set the text back maybe?
182 //fprintf( stderr, "Domain '%s' is not valid\n", str );
183 gtk_entry_set_text(GTK_ENTRY(entry), avahi_get_browse_domain() );
186 gtk_widget_set_sensitive( button, FALSE );
189 static void avahi_profiles_domain_changed(GtkWidget *entry, GtkWidget *button)
191 const char *str = gtk_entry_get_text(GTK_ENTRY(entry));
192 if(str && strcmp( str, avahi_get_browse_domain() ) != 0) {
193 // Activate the "apply" button
194 gtk_widget_set_sensitive( button, TRUE );
195 } else {
196 // deactivate the "apply" button
197 gtk_widget_set_sensitive( button, FALSE );
200 static void avahi_del_on_remove_changed(GtkToggleButton *button, gpointer user_data)
202 gint value = gtk_toggle_button_get_active(button);
204 cfg_set_single_value_as_int(config, "avahi-profiles", "delete-on-disappear", value);
207 void avahi_pref_destroy(GtkWidget *container)
209 gtk_container_remove(GTK_CONTAINER(container), pref_vbox);
212 void avahi_pref_construct(GtkWidget *container)
214 GtkWidget *entry_hbox = gtk_hbox_new(FALSE,3);
215 GtkWidget *entry = gtk_entry_new();
216 GtkWidget *apply = gtk_button_new_from_stock( GTK_STOCK_APPLY );
217 GtkWidget *del_on_remove_ck = gtk_check_button_new_with_label("Remove profile if server disappears");
218 pref_vbox = gtk_vbox_new(FALSE,6);
221 // The domain to browse
222 gtk_entry_set_text(GTK_ENTRY(entry), avahi_get_browse_domain());
224 // On change of the domain box
225 g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(avahi_profiles_domain_changed), apply);
227 // The "Apply" button
228 gtk_widget_set_sensitive( apply, FALSE );
229 g_signal_connect(G_OBJECT(apply), "clicked", G_CALLBACK(avahi_profiles_domain_applied), entry);
231 // Put the entry and label in the hbox:
232 gtk_box_pack_start(GTK_BOX(entry_hbox), gtk_label_new("Search Domain:"), FALSE, FALSE,0);
233 gtk_box_pack_start(GTK_BOX(entry_hbox), entry, FALSE, FALSE,0);
234 gtk_box_pack_start(GTK_BOX(entry_hbox), apply, FALSE, FALSE,0);
236 // Put it all in the vbox:
237 gtk_box_pack_start(GTK_BOX(pref_vbox), entry_hbox, FALSE, FALSE,0);
240 // Delete on remove checkbox
241 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(del_on_remove_ck),
242 cfg_get_single_value_as_int_with_default(config, "avahi-profiles", "delete-on-disappear", FALSE));
243 g_signal_connect(G_OBJECT(del_on_remove_ck), "toggled", G_CALLBACK(avahi_del_on_remove_changed), NULL);
244 gtk_box_pack_start(GTK_BOX(pref_vbox), del_on_remove_ck, FALSE, FALSE, 0);
247 gtk_container_add(GTK_CONTAINER(container), pref_vbox);
248 gtk_widget_show_all(container);
251 /**** Everything below here is all avahi api intergration stuff: ****/
252 static AvahiGLibPoll *glib_poll = NULL;
253 static AvahiClient *client = NULL;
254 static AvahiServiceBrowser *browser = NULL;
256 static void avahi_resolve_callback(
257 AvahiServiceResolver *r,
258 AVAHI_GCC_UNUSED AvahiIfIndex interface,
259 AVAHI_GCC_UNUSED AvahiProtocol protocol,
260 AvahiResolverEvent event,
261 const char *name,
262 const char *type,
263 const char *domain,
264 const char *host_name,
265 const AvahiAddress *address,
266 uint16_t port,
267 AvahiStringList *txt,
268 AvahiLookupResultFlags flags,
269 AVAHI_GCC_UNUSED void* userdata) {
271 assert(r);
273 /* Called whenever a service has been resolved successfully or timed out */
274 debug_printf(DEBUG_INFO,"resolved: name:%s type:%s domain:%s host_name:%s\n", name,type,domain,host_name);
275 switch (event) {
276 case AVAHI_RESOLVER_FAILURE:
277 debug_printf(DEBUG_ERROR, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
278 break;
280 case AVAHI_RESOLVER_FOUND: {
281 char a[AVAHI_ADDRESS_STR_MAX];
282 avahi_address_snprint(a, sizeof(a), address);
283 debug_printf(DEBUG_INFO,"a: %s:%s:%i\n", name,a, port);
284 avahi_add_service( name, a, port );
286 default:
287 break;
290 avahi_service_resolver_free(r);
293 static void avahi_browse_callback(
294 AvahiServiceBrowser *b,
295 AvahiIfIndex interface,
296 AvahiProtocol protocol,
297 AvahiBrowserEvent event,
298 const char *name,
299 const char *type,
300 const char *domain,
301 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
302 void* userdata) {
304 AvahiClient *c = userdata;
305 assert(b);
306 debug_printf(DEBUG_INFO,"browser callback: name:%s type:%s domain:%s\n",name,type,domain);
307 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
309 switch (event) {
310 case AVAHI_BROWSER_FAILURE:
312 debug_printf(DEBUG_ERROR, "(Browser) %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
313 //avahi_del_all_services();
314 return;
316 case AVAHI_BROWSER_NEW:
317 /* We ignore the returned resolver object. In the callback
318 function we free it. If the server is terminated before
319 the callback function is called the server will free
320 the resolver for us. */
322 if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, avahi_resolve_callback, c)))
323 debug_printf(DEBUG_WARNING, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c)));
325 break;
327 case AVAHI_BROWSER_REMOVE:
328 avahi_del_service( name );
329 break;
331 case AVAHI_BROWSER_ALL_FOR_NOW:
332 case AVAHI_BROWSER_CACHE_EXHAUSTED:
333 break;
337 static void avahi_client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) {
338 assert(c);
339 debug_printf(DEBUG_INFO,"client callback\n");
340 /* Called whenever the client or server state changes */
342 if (state == AVAHI_CLIENT_FAILURE) {
343 debug_printf(DEBUG_ERROR, "Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c)));
344 // TODO: Maybe try to reconnect here?
348 static void avahi_domain_changed(void)
350 if( browser ) {
351 avahi_service_browser_free(browser);
352 avahi_del_all_services();
355 browser = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
356 SERVICE_TYPE, avahi_get_browse_domain(), 0, avahi_browse_callback, client);
357 if( !browser ) {
358 debug_printf(DEBUG_ERROR, "Failed to create service browser for domain %s: %s\n", avahi_get_browse_domain(),
359 avahi_strerror(avahi_client_errno(client)));
363 static const char* avahi_get_default_domain()
365 if( !client )
366 return NULL;
367 return avahi_client_get_domain_name(client);
370 static void avahi_init_real() {
371 bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
372 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
373 avahi_init();
376 void avahi_init() {
377 int error;
379 if(!avahi_get_enabled()) return;
381 glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT);
383 /* Allocate a new client */
384 client = avahi_client_new(avahi_glib_poll_get(glib_poll), 0, avahi_client_callback, NULL, &error);
386 /* Check wether creating the client object succeeded */
387 if (!client) {
388 debug_printf(DEBUG_ERROR, "Failed to create client: %s\n", avahi_strerror(error));
389 return;
392 /* Create the service browser */
393 avahi_domain_changed();
396 static void avahi_destroy(void) {
397 if( browser ) {
398 avahi_service_browser_free(browser);
399 avahi_del_all_services();
400 browser = NULL;
402 if(client)
404 avahi_client_free (client);
405 client = NULL;
407 if(glib_poll)
409 avahi_glib_poll_free (glib_poll);
410 glib_poll = NULL;
414 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */