This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch main
in repository eradio.
View the commit online.
commit b367abe431a127432555081adc02bc9431502e97
Author: politebot <[email protected]>
AuthorDate: Fri Oct 10 16:53:25 2025 -0500
Modularize
---
src/Makefile.am | 2 +-
src/appdata.h | 27 +++
src/http.c | 279 ++++++++++++++++++++++++++++++
src/http.h | 10 ++
src/main.c | 496 ++---------------------------------------------------
src/radio_player.c | 63 +++++++
src/radio_player.h | 9 +
src/station_list.c | 61 +++++++
src/station_list.h | 7 +
src/ui.c | 135 +++++++++++++++
src/ui.h | 7 +
11 files changed, 611 insertions(+), 485 deletions(-)
diff --git a/src/Makefile.am b/src/Makefile.am
index 6c4934b..6005fc8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,6 @@
bin_PROGRAMS = eradio
-eradio_SOURCES = main.c
+eradio_SOURCES = main.c ui.c radio_player.c station_list.c http.c
eradio_CFLAGS = $(EFL_CFLAGS) $(LIBXML_CFLAGS)
eradio_LDADD = $(EFL_LIBS) $(LIBXML_LIBS)
\ No newline at end of file
diff --git a/src/appdata.h b/src/appdata.h
new file mode 100644
index 0000000..b7706ce
--- /dev/null
+++ b/src/appdata.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <Elementary.h>
+#include <Ecore_Evas.h>
+#include <Emotion.h>
+
+typedef struct _Station
+{
+ const char *name;
+ const char *url;
+ const char *favicon;
+ const char *stationuuid;
+} Station;
+
+typedef struct _AppData
+{
+ Evas_Object *win;
+ Evas_Object *list;
+ Evas_Object *emotion;
+ Evas_Object *search_entry;
+ Evas_Object *search_hoversel;
+ Evas_Object *play_pause_btn;
+ Evas_Object *stop_btn;
+ Evas_Object *search_btn;
+ Eina_List *stations;
+ Eina_Bool playing;
+} AppData;
diff --git a/src/http.c b/src/http.c
new file mode 100644
index 0000000..fc23964
--- /dev/null
+++ b/src/http.c
@@ -0,0 +1,279 @@
+#include <Ecore_Con.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+
+#include "http.h"
+#include "station_list.h"
+
+typedef enum _Download_Type
+{
+ DOWNLOAD_TYPE_STATIONS,
+ DOWNLOAD_TYPE_ICON
+} Download_Type;
+
+typedef struct _Download_Context
+{
+ Download_Type type;
+ AppData *ad;
+} Download_Context;
+
+typedef struct _Station_Download_Context
+{
+ Download_Context base;
+ xmlParserCtxtPtr ctxt;
+} Station_Download_Context;
+
+typedef struct _Icon_Download_Context
+{
+ Download_Context base;
+ Elm_Object_Item *list_item;
+ Eina_Binbuf *image_data;
+} Icon_Download_Context;
+
+static Eina_Bool _url_data_cb(void *data, int type, void *event_info);
+static Eina_Bool _url_complete_cb(void *data, int type, void *event_info);
+
+void
+http_init(AppData *ad)
+{
+ ecore_con_init();
+ ecore_event_handler_add(ECORE_CON_EVENT_URL_DATA, _url_data_cb, ad);
+ ecore_event_handler_add(ECORE_CON_EVENT_URL_COMPLETE, _url_complete_cb, ad);
+}
+
+void
+http_shutdown(void)
+{
+ ecore_con_shutdown();
+}
+
+void
+http_search_stations(AppData *ad, const char *search_term, const char *search_type)
+{
+ char url_str[1024];
+ Ecore_Con_Url *url;
+ Station_Download_Context *d_ctx;
+
+ if (!search_term || !search_term[0]) return;
+
+ d_ctx = calloc(1, sizeof(Station_Download_Context));
+ d_ctx->base.type = DOWNLOAD_TYPE_STATIONS;
+ d_ctx->base.ad = ad;
+
+ snprintf(url_str, sizeof(url_str), "http://de2.api.radio-browser.info/xml/stations/search?%s=%s",
+ search_type, search_term);
+
+ url = ""
+ ecore_con_url_additional_header_add(url, "User-Agent", "eradio/1.0");
+ ecore_con_url_data_set(url, d_ctx);
+ ecore_con_url_get(url);
+}
+
+void
+http_download_icon(AppData *ad, Elm_Object_Item *list_item, const char *url_str)
+{
+ Ecore_Con_Url *url = ""
+ Icon_Download_Context *icon_ctx = calloc(1, sizeof(Icon_Download_Context));
+ icon_ctx->base.type = DOWNLOAD_TYPE_ICON;
+ icon_ctx->base.ad = ad;
+ icon_ctx->list_item = list_item;
+ ecore_con_url_data_set(url, icon_ctx);
+ ecore_con_url_get(url);
+}
+
+void
+_search_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info)
+{
+ AppData *ad = data;
+ const char *search_term = elm_object_text_get(ad->search_entry);
+ const char *search_type = elm_object_text_get(ad->search_hoversel);
+ http_search_stations(ad, search_term, search_type);
+}
+
+void
+_search_entry_activated_cb(void *data, Evas_Object *obj, void *event_info)
+{
+ _search_btn_clicked_cb(data, obj, event_info);
+}
+
+static void
+_handle_station_list_data(Ecore_Con_Event_Url_Data *url_data)
+{
+ Station_Download_Context *d_ctx = ecore_con_url_data_get(url_data->url_con);
+
+ if (!d_ctx) return;
+
+ if (!d_ctx->ctxt)
+ {
+ d_ctx->ctxt = xmlCreatePushParserCtxt(NULL, NULL,
+ (const char *)url_data->data, url_data->size,
+ "noname.xml");
+ }
+ else
+ xmlParseChunk(d_ctx->ctxt, (const char *)url_data->data, url_data->size, 0);
+}
+
+static void
+_handle_icon_data(Ecore_Con_Event_Url_Data *url_data)
+{
+ Icon_Download_Context *icon_ctx = ecore_con_url_data_get(url_data->url_con);
+
+ if (!icon_ctx) return;
+
+ if (!icon_ctx->image_data)
+ icon_ctx->image_data = eina_binbuf_new();
+
+ eina_binbuf_append_length(icon_ctx->image_data, (const unsigned char *)url_data->data, url_data->size);
+}
+
+static void
+_handle_icon_complete(Ecore_Con_Event_Url_Complete *ev)
+{
+ Icon_Download_Context *icon_ctx = ecore_con_url_data_get(ev->url_con);
+
+ if (icon_ctx && icon_ctx->image_data)
+ {
+ Elm_Object_Item *it = icon_ctx->list_item;
+ Evas_Object *icon = elm_object_item_part_content_get(it, "start");
+ const char *ext = strrchr(ecore_con_url_url_get(ev->url_con), '.');
+ if (ext) ext++;
+
+ elm_image_memfile_set(icon, eina_binbuf_string_get(icon_ctx->image_data), eina_binbuf_length_get(icon_ctx->image_data), (char *)ext, NULL);
+ }
+
+ if (icon_ctx)
+ {
+ if (icon_ctx->image_data) eina_binbuf_free(icon_ctx->image_data);
+ free(icon_ctx);
+ }
+}
+
+static void
+_handle_station_list_complete(Ecore_Con_Event_Url_Complete *ev)
+{
+ Station_Download_Context *d_ctx = ecore_con_url_data_get(ev->url_con);
+ AppData *ad;
+ xmlDocPtr doc;
+ xmlXPathContextPtr xpathCtx;
+ xmlXPathObjectPtr xpathObj;
+
+ if (!d_ctx) return;
+
+ ad = d_ctx->base.ad;
+
+ if (!d_ctx->ctxt)
+ {
+ free(d_ctx);
+ return;
+ }
+
+ xmlParseChunk(d_ctx->ctxt, "", 0, 1);
+ doc = d_ctx->ctxt->myDoc;
+ xmlFreeParserCtxt(d_ctx->ctxt);
+ free(d_ctx);
+
+ if (doc == NULL)
+ {
+ printf("Error: could not parse XML\n");
+ return;
+ }
+
+ xpathCtx = xmlXPathNewContext(doc);
+ if (xpathCtx == NULL)
+ {
+ printf("Error: could not create new XPath context\n");
+ xmlFreeDoc(doc);
+ return;
+ }
+
+ xpathObj = xmlXPathEvalExpression((xmlChar *)"//station", xpathCtx);
+ if (xpathObj == NULL)
+ {
+ printf("Error: could not evaluate xpath _expression_\n");
+ xmlXPathFreeContext(xpathCtx);
+ xmlFreeDoc(doc);
+ return;
+ }
+
+ eina_list_free(ad->stations);
+ ad->stations = NULL;
+
+ for (int i = 0; i < xpathObj->nodesetval->nodeNr; i++)
+ {
+ xmlNodePtr cur = xpathObj->nodesetval->nodeTab[i];
+ Station *st = calloc(1, sizeof(Station));
+ xmlChar *prop;
+
+ prop = xmlGetProp(cur, (xmlChar *)"name");
+ if (prop)
+ {
+ st->name = eina_stringshare_add((const char *)prop);
+ xmlFree(prop);
+ }
+
+ prop = xmlGetProp(cur, (xmlChar *)"url_resolved");
+ if (prop)
+ {
+ st->url = "" char *)prop);
+ xmlFree(prop);
+ }
+
+ prop = xmlGetProp(cur, (xmlChar *)"favicon");
+ if (prop)
+ {
+ st->favicon = eina_stringshare_add((const char *)prop);
+ xmlFree(prop);
+ }
+
+ prop = xmlGetProp(cur, (xmlChar *)"stationuuid");
+ if (prop)
+ {
+ st->stationuuid = eina_stringshare_add((const char *)prop);
+ xmlFree(prop);
+ }
+ ad->stations = eina_list_append(ad->stations, st);
+ }
+
+ station_list_populate(ad, ad->stations);
+
+ xmlXPathFreeObject(xpathObj);
+ xmlXPathFreeContext(xpathCtx);
+ xmlFreeDoc(doc);
+}
+
+static Eina_Bool
+_url_data_cb(void *data, int type, void *event_info)
+{
+ Ecore_Con_Event_Url_Data *url_data = event_info;
+ Download_Context *ctx = ecore_con_url_data_get(url_data->url_con);
+
+ if (!ctx) return ECORE_CALLBACK_PASS_ON;
+
+ if (ctx->type == DOWNLOAD_TYPE_STATIONS)
+ _handle_station_list_data(url_data);
+ else if (ctx->type == DOWNLOAD_TYPE_ICON)
+ _handle_icon_data(url_data);
+
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+static Eina_Bool
+_url_complete_cb(void *data, int type, void *event_info)
+{
+ Ecore_Con_Event_Url_Complete *ev = event_info;
+ Download_Context *ctx = ecore_con_url_data_get(ev->url_con);
+
+ if (!ctx)
+ {
+ ecore_con_url_free(ev->url_con);
+ return ECORE_CALLBACK_PASS_ON;
+ }
+
+ if (ctx->type == DOWNLOAD_TYPE_STATIONS)
+ _handle_station_list_complete(ev);
+ else if (ctx->type == DOWNLOAD_TYPE_ICON)
+ _handle_icon_complete(ev);
+
+ ecore_con_url_free(ev->url_con);
+ return ECORE_CALLBACK_PASS_ON;
+}
diff --git a/src/http.h b/src/http.h
new file mode 100644
index 0000000..ed3d3a4
--- /dev/null
+++ b/src/http.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "appdata.h"
+
+void http_init(AppData *ad);
+void http_shutdown(void);
+void http_search_stations(AppData *ad, const char *search_term, const char *search_type);
+void http_download_icon(AppData *ad, Elm_Object_Item *list_item, const char *url);
+void _search_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
+void _search_entry_activated_cb(void *data, Evas_Object *obj, void *event_info);
diff --git a/src/main.c b/src/main.c
index 6ff0b53..4edf67d 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,496 +1,24 @@
-#include <Elementary.h>
-#include <Ecore_Evas.h>
-#include <Emotion.h>
-#include <Ecore_Con.h>
-#include <libxml/parser.h>
-#include <libxml/xpath.h>
-
-typedef struct _Station
-{
- const char *name;
- const char *url;
- const char *favicon;
- const char *stationuuid;
-} Station;
-
-typedef struct _AppData
-{
- Evas_Object *win;
- Evas_Object *list;
- Evas_Object *emotion;
- Evas_Object *search_entry;
- Evas_Object *search_hoversel;
- Evas_Object *play_pause_btn;
- Eina_List *stations;
- Eina_Bool playing;
-} AppData;
-
-typedef enum _Download_Type
-{
- DOWNLOAD_TYPE_STATIONS,
- DOWNLOAD_TYPE_ICON
-} Download_Type;
-
-typedef struct _Download_Context
-{
- Download_Type type;
- AppData *ad;
-} Download_Context;
-
-typedef struct _Station_Download_Context
-{
- Download_Context base;
- xmlParserCtxtPtr ctxt;
-} Station_Download_Context;
-
-typedef struct _Icon_Download_Context
-{
- Download_Context base;
- Elm_Object_Item *list_item;
- Eina_Binbuf *image_data;
-} Icon_Download_Context;
-
-
-static void _win_del_cb(void *data, Evas_Object *obj, void *event_info);
-static void _list_item_selected_cb(void *data, Evas_Object *obj, void *event_info);
-static void _play_pause_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
-static void _stop_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
-static void _hoversel_item_selected_cb(void *data, Evas_Object *obj, void *event_info);
-static void _search_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
-static void _search_entry_activated_cb(void *data, Evas_Object *obj, void *event_info);
-static Eina_Bool _url_data_cb(void *data, int type, void *event_info);
-static Eina_Bool _url_complete_cb(void *data, int type, void *event_info);
-
-
-static void
-_win_del_cb(void *data, Evas_Object *obj, void *event_info)
-{
- elm_exit();
-}
-
-static void
-_hoversel_item_selected_cb(void *data, Evas_Object *obj, void *event_info)
-{
- Elm_Object_Item *it = event_info;
- const char *label = elm_object_item_text_get(it);
- elm_object_text_set(obj, label);
-}
-
-static void
-_station_click_counter_request(Station *st)
-{
- char url_str[1024];
- Ecore_Con_Url *url;
-
- if (!st || !st->stationuuid) return;
-
- snprintf(url_str, sizeof(url_str), "http://de2.api.radio-browser.info/xml/url/%s",
- st->stationuuid);
-
- url = ""
- ecore_con_url_additional_header_add(url, "User-Agent", "eradio/1.0");
- ecore_con_url_get(url);
-}
-
-static void
-_list_item_selected_cb(void *data, Evas_Object *obj, void *event_info)
-{
- AppData *ad = data;
- Elm_Object_Item *it = event_info;
- Station *st = elm_object_item_data_get(it);
- Evas_Object *ic;
-
- if (!st) return;
-
- _station_click_counter_request(st);
-
- if (st->url && st->url[0])
- {
- emotion_object_file_set(ad->emotion, st->url);
- emotion_object_play_set(ad->emotion, EINA_TRUE);
- ad->playing = EINA_TRUE;
- ic = elm_object_part_content_get(ad->play_pause_btn, "icon");
- elm_icon_standard_set(ic, "media-playback-pause");
- }
-}
-
-static void
-_play_pause_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info)
-{
- AppData *ad = data;
- Evas_Object *ic;
-
- ad->playing = !ad->playing;
- emotion_object_play_set(ad->emotion, ad->playing);
-
- ic = elm_object_part_content_get(ad->play_pause_btn, "icon");
- if (ad->playing)
- elm_icon_standard_set(ic, "media-playback-pause");
- else
- elm_icon_standard_set(ic, "media-playback-start");
-}
-
-static void
-_search_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info)
-{
- AppData *ad = data;
- const char *search_term = elm_object_text_get(ad->search_entry);
- const char *search_type = elm_object_text_get(ad->search_hoversel);
- char url_str[1024];
- Ecore_Con_Url *url;
- Station_Download_Context *d_ctx;
-
- if (!search_term || !search_term[0]) return;
-
- d_ctx = calloc(1, sizeof(Station_Download_Context));
- d_ctx->base.type = DOWNLOAD_TYPE_STATIONS;
- d_ctx->base.ad = ad;
-
- snprintf(url_str, sizeof(url_str), "http://de2.api.radio-browser.info/xml/stations/search?%s=%s",
- search_type, search_term);
-
- url = ""
- ecore_con_url_additional_header_add(url, "User-Agent", "eradio/1.0");
- ecore_con_url_data_set(url, d_ctx);
- ecore_con_url_get(url);
-}
-
-static void
-_search_entry_activated_cb(void *data, Evas_Object *obj, void *event_info)
-{
- _search_btn_clicked_cb(data, obj, event_info);
-}
-
-static void
-_handle_station_list_data(Ecore_Con_Event_Url_Data *url_data)
-{
- Station_Download_Context *d_ctx = ecore_con_url_data_get(url_data->url_con);
-
- if (!d_ctx) return;
-
- if (!d_ctx->ctxt)
- {
- d_ctx->ctxt = xmlCreatePushParserCtxt(NULL, NULL,
- (const char *)url_data->data, url_data->size,
- "noname.xml");
- }
- else
- xmlParseChunk(d_ctx->ctxt, (const char *)url_data->data, url_data->size, 0);
-}
-
-static void
-_handle_icon_data(Ecore_Con_Event_Url_Data *url_data)
-{
- Icon_Download_Context *icon_ctx = ecore_con_url_data_get(url_data->url_con);
-
- if (!icon_ctx) return;
-
- if (!icon_ctx->image_data)
- icon_ctx->image_data = eina_binbuf_new();
-
- eina_binbuf_append_length(icon_ctx->image_data, (const unsigned char *)url_data->data, url_data->size);
-}
-
-static void
-_handle_icon_complete(Ecore_Con_Event_Url_Complete *ev)
-{
- Icon_Download_Context *icon_ctx = ecore_con_url_data_get(ev->url_con);
-
- if (icon_ctx && icon_ctx->image_data)
- {
- Elm_Object_Item *it = icon_ctx->list_item;
- Evas_Object *icon = elm_object_item_part_content_get(it, "start");
- const char *ext = strrchr(ecore_con_url_url_get(ev->url_con), '.');
- if (ext) ext++;
-
- elm_image_memfile_set(icon, eina_binbuf_string_get(icon_ctx->image_data), eina_binbuf_length_get(icon_ctx->image_data), (char *)ext, NULL);
- }
-
- if (icon_ctx)
- {
- if (icon_ctx->image_data) eina_binbuf_free(icon_ctx->image_data);
- free(icon_ctx);
- }
-}
-
-static void
-_handle_station_list_complete(Ecore_Con_Event_Url_Complete *ev)
-{
- Station_Download_Context *d_ctx = ecore_con_url_data_get(ev->url_con);
- AppData *ad;
- xmlDocPtr doc;
- xmlXPathContextPtr xpathCtx;
- xmlXPathObjectPtr xpathObj;
-
- if (!d_ctx) return;
-
- ad = d_ctx->base.ad;
-
- if (!d_ctx->ctxt)
- {
- free(d_ctx);
- return;
- }
-
- xmlParseChunk(d_ctx->ctxt, "", 0, 1);
- doc = d_ctx->ctxt->myDoc;
- xmlFreeParserCtxt(d_ctx->ctxt);
- free(d_ctx);
-
- if (doc == NULL)
- {
- printf("Error: could not parse XML\n");
- return;
- }
-
- xpathCtx = xmlXPathNewContext(doc);
- if (xpathCtx == NULL)
- {
- printf("Error: could not create new XPath context\n");
- xmlFreeDoc(doc);
- return;
- }
-
- xpathObj = xmlXPathEvalExpression((xmlChar *)"//station", xpathCtx);
- if (xpathObj == NULL)
- {
- printf("Error: could not evaluate xpath _expression_\n");
- xmlXPathFreeContext(xpathCtx);
- xmlFreeDoc(doc);
- return;
- }
-
- elm_list_clear(ad->list);
-
- for (int i = 0; i < xpathObj->nodesetval->nodeNr; i++)
- {
- xmlNodePtr cur = xpathObj->nodesetval->nodeTab[i];
- Station *st = calloc(1, sizeof(Station));
- xmlChar *prop;
-
- prop = xmlGetProp(cur, (xmlChar *)"name");
- if (prop)
- {
- st->name = eina_stringshare_add((const char *)prop);
- xmlFree(prop);
- }
-
- prop = xmlGetProp(cur, (xmlChar *)"url_resolved");
- if (prop)
- {
- st->url = "" char *)prop);
- xmlFree(prop);
- }
-
- prop = xmlGetProp(cur, (xmlChar *)"favicon");
- if (prop)
- {
- st->favicon = eina_stringshare_add((const char *)prop);
- xmlFree(prop);
- }
-
- prop = xmlGetProp(cur, (xmlChar *)"stationuuid");
- if (prop)
- {
- st->stationuuid = eina_stringshare_add((const char *)prop);
- xmlFree(prop);
- }
- ad->stations = eina_list_append(ad->stations, st);
-
- Evas_Object *icon = elm_icon_add(ad->win);
- elm_icon_standard_set(icon, "radio");
- Elm_Object_Item *li = elm_list_item_append(ad->list, st->name, icon, NULL, _list_item_selected_cb, ad);
- elm_object_item_data_set(li, st);
-
- if (st->favicon && st->favicon[0])
- {
- Ecore_Con_Url *url = ""
- Icon_Download_Context *icon_ctx = calloc(1, sizeof(Icon_Download_Context));
- icon_ctx->base.type = DOWNLOAD_TYPE_ICON;
- icon_ctx->base.ad = ad;
- icon_ctx->list_item = li;
- ecore_con_url_data_set(url, icon_ctx);
- ecore_con_url_get(url);
- }
- }
- elm_list_go(ad->list);
-
- xmlXPathFreeObject(xpathObj);
- xmlXPathFreeContext(xpathCtx);
- xmlFreeDoc(doc);
-}
-
-static Eina_Bool
-_url_data_cb(void *data, int type, void *event_info)
-{
- Ecore_Con_Event_Url_Data *url_data = event_info;
- Download_Context *ctx = ecore_con_url_data_get(url_data->url_con);
-
- if (!ctx) return ECORE_CALLBACK_PASS_ON;
-
- if (ctx->type == DOWNLOAD_TYPE_STATIONS)
- _handle_station_list_data(url_data);
- else if (ctx->type == DOWNLOAD_TYPE_ICON)
- _handle_icon_data(url_data);
-
- return ECORE_CALLBACK_PASS_ON;
-}
-
-static Eina_Bool
-_url_complete_cb(void *data, int type, void *event_info)
-{
- Ecore_Con_Event_Url_Complete *ev = event_info;
- Download_Context *ctx = ecore_con_url_data_get(ev->url_con);
-
- if (!ctx)
- {
- ecore_con_url_free(ev->url_con);
- return ECORE_CALLBACK_PASS_ON;
- }
-
- if (ctx->type == DOWNLOAD_TYPE_STATIONS)
- _handle_station_list_complete(ev);
- else if (ctx->type == DOWNLOAD_TYPE_ICON)
- _handle_icon_complete(ev);
-
- ecore_con_url_free(ev->url_con);
- return ECORE_CALLBACK_PASS_ON;
-}
-
-static void
-_stop_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info)
-{
- AppData *ad = data;
- Evas_Object *ic;
-
- emotion_object_play_set(ad->emotion, EINA_FALSE);
- emotion_object_position_set(ad->emotion, 0.0);
- ad->playing = EINA_FALSE;
- ic = elm_object_part_content_get(ad->play_pause_btn, "icon");
- elm_icon_standard_set(ic, "media-playback-start");
-}
-
-static void
-_app_exit_cb(void *data, Evas_Object *obj, void *event_info)
-{
- Ecore_Evas *ee = data;
- if (ee) ecore_evas_free(ee);
-}
-
-#include <stdlib.h>
+#include "appdata.h"
+#include "ui.h"
+#include "radio_player.h"
+#include "http.h"
EAPI_MAIN int
elm_main(int argc, char **argv)
{
AppData ad = {0};
- Ecore_Evas *ee = ecore_evas_new(NULL, 0, 0, 0, 0, NULL);
- if (ee)
- {
- int dpi = 0;
- ecore_evas_screen_dpi_get(ee, NULL, &dpi);
- if (dpi >= 192)
- elm_config_scale_set(4.0);
- }
-
-
- ecore_con_init();
elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);
- ad.win = elm_win_add(NULL, "eradio", ELM_WIN_BASIC);
- elm_win_title_set(ad.win, "eradio");
- elm_win_autodel_set(ad.win, EINA_TRUE);
- evas_object_smart_callback_add(ad.win, "delete,request", _win_del_cb, &ad);
- if (ee) evas_object_smart_callback_add(ad.win, "delete,request", _app_exit_cb, ee);
-
- ecore_event_handler_add(ECORE_CON_EVENT_URL_DATA, _url_data_cb, &ad);
- ecore_event_handler_add(ECORE_CON_EVENT_URL_COMPLETE, _url_complete_cb, &ad);
-
- Evas_Object *box = elm_box_add(ad.win);
- elm_box_padding_set(box, 0, 10);
- evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
- elm_win_resize_object_add(ad.win, box);
- evas_object_show(box);
-
- Evas_Object *search_hbox = elm_box_add(ad.win);
- elm_box_horizontal_set(search_hbox, EINA_TRUE);
- elm_box_padding_set(search_hbox, 10, 0);
- evas_object_size_hint_weight_set(search_hbox, EVAS_HINT_EXPAND, 0);
- evas_object_size_hint_align_set(search_hbox, EVAS_HINT_FILL, 0);
- elm_box_pack_end(box, search_hbox);
- evas_object_show(search_hbox);
-
- ad.search_entry = elm_entry_add(ad.win);
- elm_entry_single_line_set(ad.search_entry, EINA_TRUE);
- elm_entry_scrollable_set(ad.search_entry, EINA_TRUE);
- evas_object_size_hint_weight_set(ad.search_entry, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
- evas_object_size_hint_align_set(ad.search_entry, EVAS_HINT_FILL, EVAS_HINT_FILL);
- elm_object_part_text_set(ad.search_entry, "guide", "Search for stations...");
- elm_box_pack_end(search_hbox, ad.search_entry);
- evas_object_show(ad.search_entry);
- elm_object_focus_set(ad.search_entry, EINA_TRUE);
-
- ad.search_hoversel = elm_hoversel_add(ad.win);
- elm_hoversel_hover_parent_set(ad.search_hoversel, ad.win);
- elm_object_text_set(ad.search_hoversel, "name");
- elm_hoversel_item_add(ad.search_hoversel, "name", NULL, ELM_ICON_NONE, _hoversel_item_selected_cb, NULL);
- elm_hoversel_item_add(ad.search_hoversel, "country", NULL, ELM_ICON_NONE, _hoversel_item_selected_cb, NULL);
- elm_hoversel_item_add(ad.search_hoversel, "language", NULL, ELM_ICON_NONE, _hoversel_item_selected_cb, NULL);
- elm_hoversel_item_add(ad.search_hoversel, "tag", NULL, ELM_ICON_NONE, _hoversel_item_selected_cb, NULL);
- elm_box_pack_end(search_hbox, ad.search_hoversel);
- evas_object_show(ad.search_hoversel);
-
- Evas_Object *search_btn = elm_button_add(ad.win);
- elm_object_text_set(search_btn, "Search");
- elm_box_pack_end(search_hbox, search_btn);
- evas_object_show(search_btn);
-
- ad.list = elm_list_add(ad.win);
- evas_object_size_hint_weight_set(ad.list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
- evas_object_size_hint_align_set(ad.list, EVAS_HINT_FILL, EVAS_HINT_FILL);
- elm_box_pack_end(box, ad.list);
- evas_object_show(ad.list);
-
- ad.emotion = emotion_object_add(ad.win);
-
- Evas_Object *controls_hbox = elm_box_add(ad.win);
- elm_box_horizontal_set(controls_hbox, EINA_TRUE);
- elm_box_padding_set(controls_hbox, 10, 0);
- elm_box_align_set(controls_hbox, 0.5, 1.0);
- evas_object_size_hint_weight_set(controls_hbox, EVAS_HINT_EXPAND, 0);
- evas_object_size_hint_align_set(controls_hbox, EVAS_HINT_FILL, 0);
- elm_box_pack_end(box, controls_hbox);
- evas_object_show(controls_hbox);
-
- Evas_Object *ic;
-
- ad.play_pause_btn = elm_button_add(ad.win);
- ic = elm_icon_add(ad.play_pause_btn);
- elm_icon_standard_set(ic, "media-playback-start");
- evas_object_size_hint_min_set(ic, 40, 40);
- elm_object_part_content_set(ad.play_pause_btn, "icon", ic);
- elm_box_pack_end(controls_hbox, ad.play_pause_btn);
- evas_object_show(ad.play_pause_btn);
-
- Evas_Object *stop_btn = elm_button_add(ad.win);
- ic = elm_icon_add(stop_btn);
- elm_icon_standard_set(ic, "media-playback-stop");
- evas_object_size_hint_min_set(ic, 40, 40);
- elm_object_part_content_set(stop_btn, "icon", ic);
- elm_box_pack_end(controls_hbox, stop_btn);
- evas_object_show(stop_btn);
-
- evas_object_smart_callback_add(ad.play_pause_btn, "clicked", _play_pause_btn_clicked_cb, &ad);
- evas_object_smart_callback_add(stop_btn, "clicked", _stop_btn_clicked_cb, &ad);
- evas_object_smart_callback_add(search_btn, "clicked", _search_btn_clicked_cb, &ad);
- evas_object_smart_callback_add(ad.search_entry, "activated", _search_entry_activated_cb, &ad);
- evas_object_smart_callback_add(ad.list, "selected", _list_item_selected_cb, &ad);
-
- evas_object_resize(ad.win, 400, 600);
- evas_object_show(ad.win);
+ ui_create(&ad);
+ http_init(&ad);
+ radio_player_init(&ad);
elm_run();
- ecore_con_shutdown();
+
+ http_shutdown();
+ radio_player_shutdown();
+
return 0;
}
-ELM_MAIN()
+ELM_MAIN()
\ No newline at end of file
diff --git a/src/radio_player.c b/src/radio_player.c
new file mode 100644
index 0000000..94c153b
--- /dev/null
+++ b/src/radio_player.c
@@ -0,0 +1,63 @@
+#include "radio_player.h"
+
+void
+radio_player_init(AppData *ad)
+{
+ ad->emotion = emotion_object_add(ad->win);
+}
+
+void
+radio_player_shutdown(void)
+{
+ // Nothing to do here yet
+}
+
+void
+radio_player_play(AppData *ad, const char *url)
+{
+ if (url && url[0])
+ {
+ emotion_object_file_set(ad->emotion, url);
+ emotion_object_play_set(ad->emotion, EINA_TRUE);
+ ad->playing = EINA_TRUE;
+ Evas_Object *ic = elm_object_part_content_get(ad->play_pause_btn, "icon");
+ elm_icon_standard_set(ic, "media-playback-pause");
+ }
+}
+
+void
+radio_player_stop(AppData *ad)
+{
+ emotion_object_play_set(ad->emotion, EINA_FALSE);
+ emotion_object_position_set(ad->emotion, 0.0);
+ ad->playing = EINA_FALSE;
+ Evas_Object *ic = elm_object_part_content_get(ad->play_pause_btn, "icon");
+ elm_icon_standard_set(ic, "media-playback-start");
+}
+
+void
+radio_player_toggle_pause(AppData *ad)
+{
+ ad->playing = !ad->playing;
+ emotion_object_play_set(ad->emotion, ad->playing);
+
+ Evas_Object *ic = elm_object_part_content_get(ad->play_pause_btn, "icon");
+ if (ad->playing)
+ elm_icon_standard_set(ic, "media-playback-pause");
+ else
+ elm_icon_standard_set(ic, "media-playback-start");
+}
+
+void
+_play_pause_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info)
+{
+ AppData *ad = data;
+ radio_player_toggle_pause(ad);
+}
+
+void
+_stop_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info)
+{
+ AppData *ad = data;
+ radio_player_stop(ad);
+}
diff --git a/src/radio_player.h b/src/radio_player.h
new file mode 100644
index 0000000..983160d
--- /dev/null
+++ b/src/radio_player.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "appdata.h"
+
+void radio_player_init(AppData *ad);
+void radio_player_shutdown(void);
+void radio_player_play(AppData *ad, const char *url);
+void radio_player_stop(AppData *ad);
+void radio_player_toggle_pause(AppData *ad);
diff --git a/src/station_list.c b/src/station_list.c
new file mode 100644
index 0000000..f08c4a3
--- /dev/null
+++ b/src/station_list.c
@@ -0,0 +1,61 @@
+#include "station_list.h"
+#include "radio_player.h"
+#include "http.h"
+
+static void
+_station_click_counter_request(Station *st)
+{
+ char url_str[1024];
+ Ecore_Con_Url *url;
+
+ if (!st || !st->stationuuid) return;
+
+ snprintf(url_str, sizeof(url_str), "http://de2.api.radio-browser.info/xml/url/%s",
+ st->stationuuid);
+
+ url = ""
+ ecore_con_url_additional_header_add(url, "User-Agent", "eradio/1.0");
+ ecore_con_url_get(url);
+}
+
+void
+_list_item_selected_cb(void *data, Evas_Object *obj, void *event_info)
+{
+ AppData *ad = data;
+ Elm_Object_Item *it = event_info;
+ Station *st = elm_object_item_data_get(it);
+
+ if (!st) return;
+
+ _station_click_counter_request(st);
+ radio_player_play(ad, st->url);
+}
+
+void
+station_list_clear(AppData *ad)
+{
+ elm_list_clear(ad->list);
+}
+
+void
+station_list_populate(AppData *ad, Eina_List *stations)
+{
+ Eina_List *l;
+ Station *st;
+
+ station_list_clear(ad);
+
+ EINA_LIST_FOREACH(stations, l, st)
+ {
+ Evas_Object *icon = elm_icon_add(ad->win);
+ elm_icon_standard_set(icon, "radio");
+ Elm_Object_Item *li = elm_list_item_append(ad->list, st->name, icon, NULL, _list_item_selected_cb, ad);
+ elm_object_item_data_set(li, st);
+
+ if (st->favicon && st->favicon[0])
+ {
+ http_download_icon(ad, li, st->favicon);
+ }
+ }
+ elm_list_go(ad->list);
+}
diff --git a/src/station_list.h b/src/station_list.h
new file mode 100644
index 0000000..d544bcf
--- /dev/null
+++ b/src/station_list.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "appdata.h"
+
+void station_list_populate(AppData *ad, Eina_List *stations);
+void station_list_clear(AppData *ad);
+void _list_item_selected_cb(void *data, Evas_Object *obj, void *event_info);
diff --git a/src/ui.c b/src/ui.c
new file mode 100644
index 0000000..0ba08b8
--- /dev/null
+++ b/src/ui.c
@@ -0,0 +1,135 @@
+#include "ui.h"
+#include "appdata.h"
+
+static void _win_del_cb(void *data, Evas_Object *obj, void *event_info);
+static void _app_exit_cb(void *data, Evas_Object *obj, void *event_info);
+static void _hoversel_item_selected_cb(void *data, Evas_Object *obj, void *event_info);
+
+// Forward declarations for callbacks
+void _play_pause_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
+void _stop_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
+void _search_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
+void _search_entry_activated_cb(void *data, Evas_Object *obj, void *event_info);
+void _list_item_selected_cb(void *data, Evas_Object *obj, void *event_info);
+
+
+static void
+_win_del_cb(void *data, Evas_Object *obj, void *event_info)
+{
+ elm_exit();
+}
+
+static void
+_app_exit_cb(void *data, Evas_Object *obj, void *event_info)
+{
+ Ecore_Evas *ee = data;
+ if (ee) ecore_evas_free(ee);
+}
+
+static void
+_hoversel_item_selected_cb(void *data, Evas_Object *obj, void *event_info)
+{
+ Elm_Object_Item *it = event_info;
+ const char *label = elm_object_item_text_get(it);
+ elm_object_text_set(obj, label);
+}
+
+void
+ui_create(AppData *ad)
+{
+ Ecore_Evas *ee = ecore_evas_new(NULL, 0, 0, 0, 0, NULL);
+ if (ee)
+ {
+ int dpi = 0;
+ ecore_evas_screen_dpi_get(ee, NULL, &dpi);
+ if (dpi >= 192)
+ elm_config_scale_set(4.0);
+ }
+
+ ad->win = elm_win_add(NULL, "eradio", ELM_WIN_BASIC);
+ elm_win_title_set(ad->win, "eradio");
+ elm_win_autodel_set(ad->win, EINA_TRUE);
+ evas_object_smart_callback_add(ad->win, "delete,request", _win_del_cb, ad);
+ if (ee) evas_object_smart_callback_add(ad->win, "delete,request", _app_exit_cb, ee);
+
+ Evas_Object *box = elm_box_add(ad->win);
+ elm_box_padding_set(box, 0, 10);
+ evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ elm_win_resize_object_add(ad->win, box);
+ evas_object_show(box);
+
+ Evas_Object *search_hbox = elm_box_add(ad->win);
+ elm_box_horizontal_set(search_hbox, EINA_TRUE);
+ elm_box_padding_set(search_hbox, 10, 0);
+ evas_object_size_hint_weight_set(search_hbox, EVAS_HINT_EXPAND, 0);
+ evas_object_size_hint_align_set(search_hbox, EVAS_HINT_FILL, 0);
+ elm_box_pack_end(box, search_hbox);
+ evas_object_show(search_hbox);
+
+ ad->search_entry = elm_entry_add(ad->win);
+ elm_entry_single_line_set(ad->search_entry, EINA_TRUE);
+ elm_entry_scrollable_set(ad->search_entry, EINA_TRUE);
+ evas_object_size_hint_weight_set(ad->search_entry, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(ad->search_entry, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ elm_object_part_text_set(ad->search_entry, "guide", "Search for stations...");
+ elm_box_pack_end(search_hbox, ad->search_entry);
+ evas_object_show(ad->search_entry);
+ elm_object_focus_set(ad->search_entry, EINA_TRUE);
+
+ ad->search_hoversel = elm_hoversel_add(ad->win);
+ elm_hoversel_hover_parent_set(ad->search_hoversel, ad->win);
+ elm_object_text_set(ad->search_hoversel, "name");
+ elm_hoversel_item_add(ad->search_hoversel, "name", NULL, ELM_ICON_NONE, _hoversel_item_selected_cb, NULL);
+ elm_hoversel_item_add(ad->search_hoversel, "country", NULL, ELM_ICON_NONE, _hoversel_item_selected_cb, NULL);
+ elm_hoversel_item_add(ad->search_hoversel, "language", NULL, ELM_ICON_NONE, _hoversel_item_selected_cb, NULL);
+ elm_hoversel_item_add(ad->search_hoversel, "tag", NULL, ELM_ICON_NONE, _hoversel_item_selected_cb, NULL);
+ elm_box_pack_end(search_hbox, ad->search_hoversel);
+ evas_object_show(ad->search_hoversel);
+
+ ad->search_btn = elm_button_add(ad->win);
+ elm_object_text_set(ad->search_btn, "Search");
+ elm_box_pack_end(search_hbox, ad->search_btn);
+ evas_object_show(ad->search_btn);
+
+ ad->list = elm_list_add(ad->win);
+ evas_object_size_hint_weight_set(ad->list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(ad->list, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ elm_box_pack_end(box, ad->list);
+ evas_object_show(ad->list);
+
+ Evas_Object *controls_hbox = elm_box_add(ad->win);
+ elm_box_horizontal_set(controls_hbox, EINA_TRUE);
+ elm_box_padding_set(controls_hbox, 10, 0);
+ elm_box_align_set(controls_hbox, 0.5, 1.0);
+ evas_object_size_hint_weight_set(controls_hbox, EVAS_HINT_EXPAND, 0);
+ evas_object_size_hint_align_set(controls_hbox, EVAS_HINT_FILL, 0);
+ elm_box_pack_end(box, controls_hbox);
+ evas_object_show(controls_hbox);
+
+ Evas_Object *ic;
+
+ ad->play_pause_btn = elm_button_add(ad->win);
+ ic = elm_icon_add(ad->play_pause_btn);
+ elm_icon_standard_set(ic, "media-playback-start");
+ evas_object_size_hint_min_set(ic, 40, 40);
+ elm_object_part_content_set(ad->play_pause_btn, "icon", ic);
+ elm_box_pack_end(controls_hbox, ad->play_pause_btn);
+ evas_object_show(ad->play_pause_btn);
+
+ ad->stop_btn = elm_button_add(ad->win);
+ ic = elm_icon_add(ad->stop_btn);
+ elm_icon_standard_set(ic, "media-playback-stop");
+ evas_object_size_hint_min_set(ic, 40, 40);
+ elm_object_part_content_set(ad->stop_btn, "icon", ic);
+ elm_box_pack_end(controls_hbox, ad->stop_btn);
+ evas_object_show(ad->stop_btn);
+
+ evas_object_smart_callback_add(ad->play_pause_btn, "clicked", _play_pause_btn_clicked_cb, ad);
+ evas_object_smart_callback_add(ad->stop_btn, "clicked", _stop_btn_clicked_cb, ad);
+ evas_object_smart_callback_add(ad->search_btn, "clicked", _search_btn_clicked_cb, ad);
+ evas_object_smart_callback_add(ad->search_entry, "activated", _search_entry_activated_cb, ad);
+ evas_object_smart_callback_add(ad->list, "selected", _list_item_selected_cb, ad);
+
+ evas_object_resize(ad->win, 400, 600);
+ evas_object_show(ad->win);
+}
diff --git a/src/ui.h b/src/ui.h
new file mode 100644
index 0000000..0c7ef27
--- /dev/null
+++ b/src/ui.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <Elementary.h>
+
+typedef struct _AppData AppData;
+
+void ui_create(AppData *ad);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.