1 /* gmpc-wikipedia (GMPC plugin)
2 * Copyright (C) 2006-2009 Qball Cow <qball@sarine.nl>
3 * Project homepage: http://gmpcwiki.sarine.nl/
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include <glib/gi18n-lib.h>
25 #include <gmpc/plugin.h>
26 #include <gmpc/gmpc_easy_download.h>
30 #include <webkit/webkit.h>
33 #include <WebKit/webkit.h>
38 #include <libxml/parser.h>
39 #include <libxml/tree.h>
41 #include <libsoup/soup.h>
42 static void wp_add(GtkWidget
*cat_tree
);
43 static void wp_selected(GtkWidget
*container
);
44 static void wp_unselected(GtkWidget
*container
);
45 static void wp_changed(GtkWidget
*tree
, GtkTreeIter
*iter
);
46 static void wp_status_changed(MpdObj
*mi
, ChangedStatusType what
, void *userdata
);
47 static void wp_query_callback(const GEADAsyncHandler
*handle
, GEADStatus status
, gpointer data
);
48 static xmlNodePtr
get_first_node_by_name(xmlNodePtr xml
, gchar
*name
);
50 static GtkWidget
*moz
= NULL
;
51 static GtkWidget
*wp_vbox
= NULL
;
52 static GtkWidget
*pgb
= NULL
;
54 static void wp_start_init(void);
55 static int wp_get_enabled();
56 static void wp_set_enabled(int enabled
);
57 static int global_progress
;
59 static gchar
*old_artist
;
60 static gchar locale
[3];
61 static GRegex
*page_guess_re
;
62 static const char *wikipedia_langs
[] = {"ar", "az", "bg", "bn", "ca", "cs", "de", "el", "en", "eo", "es", "fa", "fi", "fr", "gu", "he", "hi", "id", "mr", "it", "ja", "lt", "ms", "mk", "pa", "pl", "pt", "ro", "ru", "sv", "ta", "th", "tr", "uk", "vi", "yo", "zh"};
64 /* Needed plugin_wp stuff */
65 gmpcPlBrowserPlugin wp_gbp
= {
67 .selected
= wp_selected
,
68 .unselected
= wp_unselected
,
70 int plugin_api_version
= PLUGIN_API_VERSION
;
72 static const gchar
*wp_get_translation_domain(void)
74 return GETTEXT_PACKAGE
;
78 .name
= N_("WikiPedia"),
79 .version
= {PLUGIN_MAJOR_VERSION
,PLUGIN_MINOR_VERSION
,PLUGIN_MICRO_VERSION
},
80 .plugin_type
= GMPC_PLUGIN_PL_BROWSER
,
81 .init
= wp_start_init
, /*init */
83 .mpd_status_changed
= wp_status_changed
, /* status changed */
84 .get_enabled
= wp_get_enabled
,
85 .set_enabled
= wp_set_enabled
,
86 .get_translation_domain
= wp_get_translation_domain
90 /* Playlist window row reference */
91 static GtkTreeRowReference
*wiki_ref
= NULL
;
92 #define WP_SKINSTR "?useskin=chick"
93 static gchar
*current_url
= NULL
;
95 static void wp_set_url(char *wp
)
99 new_url
= malloc(strlen(wp
) + strlen(WP_SKINSTR
) + 1);
101 strcat(new_url
, WP_SKINSTR
);
105 current_url
= soup_uri_decode(new_url
);
107 webkit_web_view_open(WEBKIT_WEB_VIEW(moz
), new_url
);
108 gtk_widget_show_all(moz
);
112 static void wp_query_callback(const GEADAsyncHandler
*handle
, GEADStatus status
, gpointer ignored
)
116 xmlNodePtr root
, sect
, desc
, url
, c1
, query
;
118 gchar
*targeturl
, *txt
;
120 if(status
!= GEAD_DONE
)
122 data
= gmpc_easy_handler_get_data(handle
, &size
);
123 doc
= xmlParseMemory(data
, size
);
126 root
= xmlDocGetRootElement(doc
);
129 sect
= get_first_node_by_name(root
,"Section");
133 for(c1
= sect
->xmlChildrenNode
; c1
; c1
= c1
->next
)
135 desc
= get_first_node_by_name(c1
, "Text");
136 url
= get_first_node_by_name(c1
, "Url");
139 txt
= xmlNodeListGetString(doc
, desc
->xmlChildrenNode
, 1);
142 if (g_regex_match_full(page_guess_re
, txt
, strlen(txt
), 0, 0, NULL
, NULL
) &&
143 xmlNodeListGetString(doc
, url
->xmlChildrenNode
, 1))
145 wp_set_url(xmlNodeListGetString(doc
, url
->xmlChildrenNode
, 1));
150 /* nothing was found by regex, use first entry */
152 c1
= sect
->xmlChildrenNode
;
154 url
= get_first_node_by_name(c1
, "Url");
155 if (url
&& xmlNodeListGetString(doc
, url
->xmlChildrenNode
, 1))
156 wp_set_url(xmlNodeListGetString(doc
,url
->xmlChildrenNode
,1));
158 /* nothing is found, if we are localized, grab our search string back and do some magic */
159 query
= get_first_node_by_name(root
, "Query");
162 txt
= xmlNodeListGetString(doc
, query
->xmlChildrenNode
, 1);
166 /* fist try english wikipedia, it's the biggest after all */
167 const gchar
*oldurl
= gmpc_easy_handler_get_uri(handle
);
168 if (!g_str_has_prefix(oldurl
, "http://en.")) {
169 gchar
*newurl
= g_strdup_printf("http://en.wikipedia.org/w/api.php?action=opensearch&search=%s&format=xml", txt
);
170 debug_printf(DEBUG_INFO
, "Trying to fetch: %s\n", newurl
);
171 gmpc_easy_async_downloader(newurl
, wp_query_callback
, NULL
);
175 /* nothing is found, display localized wikipedia
176 * "unknown article" page. Not loading anything is
178 targeturl
= g_strdup_printf("http://%s.wikipedia.org/wiki/%s", locale
, txt
);
179 wp_set_url(targeturl
);
187 /* We need to remove &, ? and /, the rest goes to the webkit widget */
188 static gchar
*wp_clean_for_url(const gchar
*str
)
192 GError
*error
= NULL
;
195 re
= g_regex_new("[&/\\?]", G_REGEX_MULTILINE
, 0, &error
);
197 debug_printf(DEBUG_INFO
, "Build regexp %s\n", error
->message
);
203 nstr
= g_regex_replace(re
, str
, strlen(str
), 0, "", 0, &error
);
205 debug_printf(DEBUG_INFO
, "regexp replace %s\n", error
->message
);
212 void wp_changed(GtkWidget
*tree
, GtkTreeIter
*iter
)
214 mpd_Song
*song
= mpd_playlist_get_current_song(connection
);
215 gchar
*artist
= NULL
;
220 /* copied from metadata.h */
222 if (cfg_get_single_value_as_int_with_default(config
, "metadata", "rename", FALSE
))
224 gchar
**str
= g_strsplit(song
->artist
, ",", 2);
226 artist
= g_strdup_printf("%s %s", g_strstrip(str
[1]), g_strstrip(str
[0]));
228 artist
= g_strdup(song
->artist
);
230 debug_printf(DEBUG_INFO
, "string converted to: '%s'", artist
);
232 artist
= g_strdup(song
->artist
);
238 if (strcmp(old_artist
, "NONE") != 0)
242 current_url
= g_strdup("http://www.musicpd.org/");
243 webkit_web_view_open(WEBKIT_WEB_VIEW(moz
), current_url
);
244 old_artist
= g_strdup("NONE");
249 gchar
*esc_artist
= wp_clean_for_url(artist
);
251 if (strcmp(old_artist
, esc_artist
) != 0)
253 gchar
*url
= g_strdup_printf("http://%s.wikipedia.org/w/api.php?action=opensearch&search=%s&format=xml", locale
, esc_artist
);
254 debug_printf(DEBUG_INFO
, "Trying to fetch: %s\n", url
);
255 gmpc_easy_async_downloader(url
, wp_query_callback
, NULL
);
259 old_artist
= g_strdup(esc_artist
);
264 void wp_progress(GtkWidget
*mozem
, int progress
, gpointer data
)
266 float fprog
= (float)progress
/100.0;
267 gchar
*text
= g_strdup_printf("%d %%",progress
);
268 global_progress
=progress
;
269 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pgb
),fprog
);
270 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pgb
),text
);
274 void wp_progress_started(WebKitWebView
*webkitwebview
,WebKitWebFrame
*arg1
,gpointer user_data
)
276 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pgb
),0);
277 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pgb
),"0 %");
278 gtk_widget_show(pgb
);
281 void wp_progress_finished(WebKitWebView
*webkitwebview
,WebKitWebFrame
282 *arg1
,gpointer user_data
)
284 gtk_widget_hide(pgb
);
287 /* open urls in external window. You don't want to navigate without a back
288 * button nor url bar.
289 * No clean way to handle this, so use global cur_url
291 static WebKitNavigationResponse
wp_navigation_requested(WebKitWebView
*web_view
,
292 WebKitWebFrame
*frame
, WebKitNetworkRequest
*request
,
297 GError
*error
= NULL
;
300 uri
= webkit_network_request_get_uri(request
);
301 decoded_uri
= soup_uri_decode(uri
);
302 if (g_str_has_prefix(decoded_uri
, current_url
)) {
303 debug_printf(DEBUG_INFO
, "Accepting %s\n", uri
);
305 return WEBKIT_NAVIGATION_RESPONSE_ACCEPT
;
309 debug_printf(DEBUG_INFO
, "%s != %s\n", uri
, current_url
);
311 screen
= gtk_widget_get_screen(GTK_WIDGET(web_view
));
313 screen
= gdk_screen_get_default ();
315 gtk_show_uri(screen
, uri
, gtk_get_current_event_time(), &error
);
317 debug_printf(DEBUG_INFO
, "gtk_show_uri %s\n", error
->message
);
320 return WEBKIT_NAVIGATION_RESPONSE_IGNORE
;
325 GError
*error
= NULL
;
326 GtkWidget
*sw
=gtk_scrolled_window_new(NULL
,NULL
);
329 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw
), GTK_SHADOW_ETCHED_IN
);
330 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
), GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
331 wp_vbox
= gtk_vbox_new(FALSE
, 6);
332 moz
= webkit_web_view_new();//moz_new();
336 webkit_web_view_can_go_back_or_forward(WEBKIT_WEB_VIEW(moz
), 0);
338 gtk_container_add(GTK_CONTAINER(sw
), moz
);
339 gtk_box_pack_start_defaults(GTK_BOX(wp_vbox
), sw
);
340 pgb
= gtk_progress_bar_new();
341 gtk_box_pack_start(GTK_BOX(wp_vbox
), pgb
, FALSE
, TRUE
, 0);
342 gtk_widget_grab_focus (GTK_WIDGET (moz
));
343 g_signal_connect(moz
, "load_progress_changed", G_CALLBACK(wp_progress
), NULL
);
344 g_signal_connect(moz
, "load_finished", G_CALLBACK(wp_progress_finished
), NULL
);
345 g_signal_connect(moz
, "load_started", G_CALLBACK(wp_progress_started
), NULL
);
346 g_signal_connect(moz
, "navigation-requested", G_CALLBACK(wp_navigation_requested
), NULL
);
347 gtk_widget_show_all(wp_vbox
);
348 gtk_widget_hide(pgb
);
349 g_object_ref(G_OBJECT(wp_vbox
));
350 old_artist
= g_strdup("NONE");
351 /* we should add inernationalized artist|band|musician|singer|rapper|group in the regex */
352 page_guess_re
= g_regex_new("\\(.*(artist|band|musician|singer|rapper|group).*\\)", G_REGEX_CASELESS
, 0, &error
);
356 l
= setlocale(LC_ALL
,"");
360 for (i
= 0; i
< sizeof(wikipedia_langs
) / sizeof(char *); i
++) {
361 if (strncmp(l
, wikipedia_langs
[i
], 2) == 0) {
370 void wp_add(GtkWidget
*cat_tree
)
372 GtkTreePath
*path
= NULL
;
373 GtkListStore
*pl3_tree
= (GtkListStore
*)gtk_tree_view_get_model(GTK_TREE_VIEW(cat_tree
));
375 if(!cfg_get_single_value_as_int_with_default(config
, "wp-plugin", "enable", 0)) return;
376 gtk_list_store_append(pl3_tree
, &iter
);
377 gtk_list_store_set(pl3_tree
, &iter
,
378 PL3_CAT_TYPE
, plugin
.id
,
379 PL3_CAT_TITLE
, _("Wikipedia Lookup"),
380 PL3_CAT_ICON_ID
, "wikipedia",
384 gtk_tree_row_reference_free(wiki_ref
);
388 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(playlist3_get_category_tree_store()), &iter
);
391 wiki_ref
= gtk_tree_row_reference_new(GTK_TREE_MODEL(playlist3_get_category_tree_store()), path
);
392 gtk_tree_path_free(path
);
397 void wp_selected(GtkWidget
*container
)
405 gtk_container_add(GTK_CONTAINER(container
), wp_vbox
);
406 gtk_widget_show_all(wp_vbox
);
407 if(global_progress
==100)
409 gtk_widget_hide(pgb
);
413 void wp_unselected(GtkWidget
*container
)
415 gtk_widget_hide(moz
);
416 gtk_container_remove(GTK_CONTAINER(container
),wp_vbox
);
420 void wp_set_enabled(int enabled
)
422 cfg_set_single_value_as_int(config
, "wp-plugin", "enable", enabled
);
427 wp_add(GTK_WIDGET(playlist3_get_category_tree_view()));
432 GtkTreePath
*path
= gtk_tree_row_reference_get_path(wiki_ref
);
435 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(playlist3_get_category_tree_store()), &iter
, path
)){
436 gtk_list_store_remove(playlist3_get_category_tree_store(), &iter
);
438 gtk_tree_path_free(path
);
439 gtk_tree_row_reference_free(wiki_ref
);
447 return cfg_get_single_value_as_int_with_default(config
, "wp-plugin", "enable", 0);
449 static void wp_start_init(void)
451 gchar
*path
= gmpc_plugin_get_data_path(&plugin
);
452 gchar
*url
= g_build_path(G_DIR_SEPARATOR_S
,path
, "wikipedia", NULL
);
453 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default (),url
);
457 bindtextdomain(GETTEXT_PACKAGE
, PACKAGE_LOCALE_DIR
);
458 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8");
462 static void wp_status_changed(MpdObj
*mi
, ChangedStatusType what
, void *userdata
)
464 if(pl3_cat_get_selected_browser() == plugin
.id
)
466 wp_changed(NULL
, NULL
);
470 static xmlNodePtr
get_first_node_by_name(xmlNodePtr xml
, gchar
*name
) {
471 if(name
== NULL
) return NULL
;
473 xmlNodePtr c
= xml
->xmlChildrenNode
;
475 if(c
->name
&& xmlStrEqual(c
->name
, (xmlChar
*) name
))