diff options
-rw-r--r-- | Makefile | 43 | ||||
-rw-r--r-- | cfmradio.c | 263 | ||||
-rw-r--r-- | data/cfmradio.desktop | 9 | ||||
-rw-r--r-- | data/cfmradio.service | 3 | ||||
-rw-r--r-- | data/codetables.utf8 | 14 | ||||
-rw-r--r-- | data/icon.png | bin | 0 -> 2916 bytes | |||
-rw-r--r-- | data/make-table.c | 32 | ||||
-rw-r--r-- | debian/changelog | 5 | ||||
-rw-r--r-- | debian/compat | 1 | ||||
-rw-r--r-- | debian/control | 79 | ||||
-rw-r--r-- | debian/copyright | 26 | ||||
-rw-r--r-- | debian/dirs | 4 | ||||
-rw-r--r-- | debian/docs | 0 | ||||
-rwxr-xr-x | debian/rules | 70 | ||||
-rw-r--r-- | n900-fmrx-enabler.h | 65 | ||||
-rw-r--r-- | n900-fmrx-enabler.xml | 38 | ||||
-rw-r--r-- | preset_list.c | 149 | ||||
-rw-r--r-- | preset_list.h | 37 | ||||
-rw-r--r-- | presets.c | 344 | ||||
-rw-r--r-- | presets.h | 45 | ||||
-rw-r--r-- | radio.c | 666 | ||||
-rw-r--r-- | radio.h | 37 | ||||
-rw-r--r-- | rds.c | 281 | ||||
-rw-r--r-- | rds.h | 8 | ||||
-rw-r--r-- | tuner.c | 289 | ||||
-rw-r--r-- | tuner.h | 36 | ||||
-rw-r--r-- | types.c | 22 | ||||
-rw-r--r-- | types.h | 22 |
28 files changed, 2588 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a47812f --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +CFLAGS?=-Os -g -Wall +LDFLAGS?=-Wl,--as-needed + +MISC_CFLAGS:=-std=gnu99 -DG_LOG_DOMAIN=\"CFmRadio\" +PKGCONFIG_PKGS:=libosso hildon-1 alsa libpulse-mainloop-glib dbus-glib-1 gconf-2.0 +PKGCONFIG_CFLAGS:=$(shell pkg-config $(PKGCONFIG_PKGS) --cflags) +PKGCONFIG_LIBS:=$(shell pkg-config $(PKGCONFIG_PKGS) --libs) +LAUNCHER_CFLAGS:=$(shell pkg-config maemo-launcher-app --cflags) +LAUNCHER_LDFLAGS:=$(shell pkg-config maemo-launcher-app --libs) + +OBJS=cfmradio.o radio.o types.o tuner.o rds.o presets.o preset_list.o + +all: cfmradio.launch + +cfmradio.launch: $(OBJS) + $(CC) $(LAUNCHER_LDFLAGS) $(LDFLAGS) -o $@ $^ $(PKGCONFIG_LIBS) $(LIBS) + +cfmradio: $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(PKGCONFIG_LIBS) $(LIBS) + +$(OBJS): %.o: %.c + $(CC) $(MISC_CFLAGS) $(PKGCONFIG_CFLAGS) $(LAUNCHER_CFLAGS) $(CFLAGS) -o $@ -c $< + +radio.c: radio.h types.h n900-fmrx-enabler.h + +n900-fmrx-enabler.h: n900-fmrx-enabler.xml + dbus-binding-tool --mode=glib-client --output=$@ --prefix=fmrx_enabler $< + +install: cfmradio.launch + install -m 0755 $(IFLAGS) cfmradio.launch $(DESTDIR)/usr/bin/ + ln -sf /usr/bin/maemo-invoker $(DESTDIR)/usr/bin/cfmradio + install -m 0644 $(IFLAGS) data/icon.png \ + $(DESTDIR)/usr/share/icons/hicolor/scalable/hildon/cfmradio.png + install -m 0644 $(IFLAGS) data/cfmradio.service \ + $(DESTDIR)/usr/share/dbus-1/services/ + install -m 0644 $(IFLAGS) data/cfmradio.desktop \ + $(DESTDIR)/usr/share/applications/hildon/ + +clean: + rm -f cfmradio cfmradio.launch *.o + +.PHONY: all clean + diff --git a/cfmradio.c b/cfmradio.c new file mode 100644 index 0000000..a269b53 --- /dev/null +++ b/cfmradio.c @@ -0,0 +1,263 @@ +#include <libosso.h> +#include <hildon/hildon.h> + +#include "radio.h" +#include "presets.h" +#include "preset_list.h" +#include "tuner.h" +#include "types.h" + +static osso_context_t *osso_context; +static HildonProgram *program; +static HildonWindow *main_window; + +static CFmRadio *radio; +static CFmPresets *presets; + +static GtkLabel *freq_label; +static GtkLabel *ps_label, *rt_label; +static CFmTuner *tuner; +//static GtkToolbar *toolbar; + +static CFmPresetList *preset_list; + +static guint rds_timer; + +static void print_freq(gulong freq) +{ + static gchar markup[256]; + float freq_mhz; + + freq_mhz = freq / 1000000.0f; + + g_snprintf(markup, sizeof(markup), "<span font=\"64\">%.1f</span> MHz", freq_mhz); + gtk_label_set_markup(freq_label, markup); +} + +static void print_rds() +{ + gchar *rds_ps, *rds_rt; + gchar *markup; + + g_object_get(G_OBJECT(radio), "rds-ps", &rds_ps, "rds-rt", &rds_rt, NULL); + + markup = g_markup_printf_escaped("<span font=\"31\">%s</span>", + g_strstrip(rds_ps)); + gtk_label_set_markup(ps_label, markup); + g_free(markup); + + + gtk_label_set_text(rt_label, g_strstrip(rds_rt)); + + g_free(rds_ps); + g_free(rds_rt); +} + +static void range_low_changed_cb(GObject *object, GParamSpec *psec, gpointer user_data) +{ + gulong freq; + g_object_get(G_OBJECT(radio), "range-low", &freq, NULL); + g_object_set(G_OBJECT(tuner), "range-low", freq, NULL); +} + +static void range_high_changed_cb(GObject *object, GParamSpec *psec, gpointer user_data) +{ + gulong freq; + g_object_get(G_OBJECT(radio), "range-high", &freq, NULL); + g_object_set(G_OBJECT(tuner), "range-high", freq, NULL); +} + +static void frequency_changed_cb(GObject *object, GParamSpec *psec, gpointer user_data) +{ + gulong freq; + g_object_get(G_OBJECT(radio), "frequency", &freq, NULL); + g_object_set(G_OBJECT(tuner), "frequency", freq, NULL); + print_freq(freq); +} + +static gboolean key_press_cb(GObject *object, GdkEventKey *event, gpointer user_data) +{ + gulong freq; + switch (event->keyval) { + case GDK_Left: + g_object_get(G_OBJECT(radio), "frequency", &freq, NULL); + freq -= 100000; + g_object_set(G_OBJECT(radio), "frequency", freq, NULL); + return FALSE; + break; + case GDK_Right: + g_object_get(G_OBJECT(radio), "frequency", &freq, NULL); + freq += 100000; + g_object_set(G_OBJECT(radio), "frequency", freq, NULL); + return FALSE; + break; + } + return TRUE; +} + +static gboolean tuner_changed_cb(GObject *object, gpointer user_data) +{ + gulong freq; + g_object_get(G_OBJECT(tuner), "frequency", &freq, NULL); + print_freq(freq); + return TRUE; +} + +static gboolean tuner_tuned_cb(GObject *object, gpointer user_data) +{ + gulong freq; + g_object_get(G_OBJECT(tuner), "frequency", &freq, NULL); + g_object_set(G_OBJECT(radio), "frequency", freq, NULL); + print_freq(freq); + return TRUE; +} + +static gboolean rds_timer_cb(gpointer data) +{ + print_rds(); + return TRUE; +} + +static void presets_clicked(GtkButton *button, gpointer user_data) +{ + cfm_preset_list_show_for(preset_list, presets); +} + +static void preset_frequency_cb(GObject *object, GParamSpec *psec, gpointer user_data) +{ + gulong freq; + g_object_get(G_OBJECT(preset_list), "frequency", &freq, NULL); + g_object_set(G_OBJECT(radio), "frequency", freq, NULL); + gtk_widget_hide(GTK_WIDGET(preset_list)); + print_freq(freq); +} + +static void add_preset_clicked(GtkButton *button, gpointer user_data) +{ + gchar *rds_ps = NULL; + gulong freq; + + g_object_get(G_OBJECT(radio), "rds-ps", &rds_ps, "frequency", &freq, NULL); + cfm_presets_set_preset(presets, freq, g_strstrip(rds_ps)); + + g_free(rds_ps); +} + +static void build_main_window() +{ + GtkBox *box; + + main_window = HILDON_WINDOW(hildon_stackable_window_new()); + gtk_window_set_title(GTK_WINDOW(main_window), "Radio"); + hildon_gtk_window_set_portrait_flags(GTK_WINDOW(main_window), + HILDON_PORTRAIT_MODE_SUPPORT); + + HildonAppMenu *menu = HILDON_APP_MENU(hildon_app_menu_new()); + + GtkWidget *menu_button; + menu_button = gtk_button_new_with_label("Presets..."); + g_signal_connect_after(G_OBJECT(menu_button), "clicked", + G_CALLBACK(presets_clicked), NULL); + hildon_app_menu_append(menu, GTK_BUTTON(menu_button)); + menu_button = gtk_button_new_with_label("Add preset"); + g_signal_connect_after(G_OBJECT(menu_button), "clicked", + G_CALLBACK(add_preset_clicked), NULL); + hildon_app_menu_append(menu, GTK_BUTTON(menu_button)); + gtk_widget_show_all(GTK_WIDGET(menu)); + + box = GTK_BOX(gtk_vbox_new(FALSE, 0)); + + freq_label = GTK_LABEL(gtk_label_new(NULL)); + ps_label = GTK_LABEL(gtk_label_new(NULL)); + rt_label = GTK_LABEL(gtk_label_new(NULL)); + tuner = cfm_tuner_new(); + +#if 0 + toolbar = GTK_TOOLBAR(gtk_toolbar_new()); + + gtk_toolbar_set_icon_size(toolbar, HILDON_ICON_SIZE_THUMB); + + GtkToolItem* item = gtk_tool_button_new( + gtk_image_new_from_icon_name("general_back", HILDON_ICON_SIZE_THUMB), + NULL); + gtk_toolbar_insert(toolbar, item, -1); + item = gtk_tool_item_new(); + gtk_tool_item_set_expand(item, TRUE); + gtk_toolbar_insert(toolbar, item, -1); + item = gtk_tool_button_new(gtk_image_new_from_icon_name( + "general_add", HILDON_ICON_SIZE_THUMB), NULL); + gtk_toolbar_insert(toolbar, item, -1); + item = gtk_tool_button_new(gtk_image_new_from_icon_name( + "general_mybookmarks_folder", HILDON_ICON_SIZE_THUMB), NULL); + gtk_toolbar_insert(toolbar, item, -1); + item = gtk_tool_item_new(); + gtk_tool_item_set_expand(item, TRUE); + gtk_toolbar_insert(toolbar, item, -1); + item = gtk_tool_button_new(gtk_image_new_from_icon_name( + "general_forward", HILDON_ICON_SIZE_THUMB), NULL); + gtk_toolbar_insert(toolbar, item, -1); +#endif + + gtk_box_pack_start(box, GTK_WIDGET(freq_label), TRUE, TRUE, 0); + gtk_box_pack_start(box, GTK_WIDGET(ps_label), FALSE, FALSE, 0); + gtk_box_pack_start(box, GTK_WIDGET(rt_label), FALSE, FALSE, 0); + gtk_box_pack_start(box, GTK_WIDGET(tuner), FALSE, FALSE, 0); + gtk_container_add(GTK_CONTAINER(main_window), GTK_WIDGET(box)); + + hildon_window_set_app_menu(main_window, menu); + + //hildon_window_add_toolbar(main_window, toolbar); + hildon_program_add_window(program, main_window); + + g_signal_connect(G_OBJECT(main_window), "delete-event", + G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect(G_OBJECT(main_window), "key-press-event", + G_CALLBACK(key_press_cb), NULL); + g_signal_connect(G_OBJECT(tuner), "frequency-changed", + G_CALLBACK(tuner_changed_cb), NULL); + g_signal_connect(G_OBJECT(tuner), "frequency-tuned", + G_CALLBACK(tuner_tuned_cb), NULL); +} + +int main(int argc, char *argv[]) +{ + hildon_gtk_init(&argc, &argv); + + osso_context = osso_initialize("com.javispedro.cfmradio", "0.1", TRUE, NULL); + g_warn_if_fail(osso_context != NULL); + + g_set_application_name("FM Radio"); /* This might be important for Pulse */ + program = hildon_program_get_instance(); + + radio = cfm_radio_new(); + g_signal_connect(G_OBJECT(radio), "notify::range-low", + G_CALLBACK(range_low_changed_cb), NULL); + g_signal_connect(G_OBJECT(radio), "notify::range-high", + G_CALLBACK(range_high_changed_cb), NULL); + g_signal_connect(G_OBJECT(radio), "notify::frequency", + G_CALLBACK(frequency_changed_cb), NULL); + + presets = cfm_presets_get_default(); + + rds_timer = g_timeout_add_seconds(1, rds_timer_cb, NULL); + + build_main_window(); + + gtk_widget_show_all(GTK_WIDGET(main_window)); + + preset_list = cfm_preset_list_new(); + g_signal_connect(G_OBJECT(preset_list), "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), NULL); + g_signal_connect(G_OBJECT(preset_list), "notify::frequency", + G_CALLBACK(preset_frequency_cb), NULL); + + g_object_set(G_OBJECT(radio), "output", CFM_RADIO_OUTPUT_SYSTEM, NULL); + + gtk_main(); + + g_object_unref(G_OBJECT(radio)); + osso_deinitialize(osso_context); + + return 0; +} + diff --git a/data/cfmradio.desktop b/data/cfmradio.desktop new file mode 100644 index 0000000..3f342e9 --- /dev/null +++ b/data/cfmradio.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Encoding=UTF-8 +Version=1.0 +Type=Application +Name=Radio +Exec=/usr/bin/cfmradio +Icon=cfmradio +X-Osso-Service=com.javispedro.cfmradio +X-Osso-Type=application/x-executable diff --git a/data/cfmradio.service b/data/cfmradio.service new file mode 100644 index 0000000..6c94f2b --- /dev/null +++ b/data/cfmradio.service @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=com.javispedro.cfmradio +Exec=/usr/bin/cfmradio diff --git a/data/codetables.utf8 b/data/codetables.utf8 new file mode 100644 index 0000000..0be19e3 --- /dev/null +++ b/data/codetables.utf8 @@ -0,0 +1,14 @@ + !"#¤%&'()*+,-./ +0123456789:;<=>? +@ABCDEFGHIJKLMNO +PQRSTUVWXYZ[\] + abcdefghijklmno +pqrstuvwxyz{|} +áàéèíìóòúùÑÇŞß¡IJ +âäêëîïôöûüñçşğ?ij +ªα©‰Ǧĕňőπ?£$←↑→↓ +º¹²³±İńűμ¿÷°¼½¾§ +ÁÀÉÈÍÌÓÒÚÙŘČŠŽĐĽ +ÂÄÊËÎÏÔÖÛÜřčšžďľ +ÃÅÆŒŷÝÕØþŊŔĆŚŹ?ð +ãåæœŵýõøþŋŕćśź? diff --git a/data/icon.png b/data/icon.png Binary files differnew file mode 100644 index 0000000..7f9cbc1 --- /dev/null +++ b/data/icon.png diff --git a/data/make-table.c b/data/make-table.c new file mode 100644 index 0000000..9afca7d --- /dev/null +++ b/data/make-table.c @@ -0,0 +1,32 @@ +#include <glib.h> + +int main(int argc, char **argv) { + GError *error = NULL; + gchar *data; + if (!g_file_get_contents("codetables.utf8", &data, NULL, &error)) { + g_printerr("Failed to open %s: %s\n", error->message); + return 1; + } + + gint i; + guint count = 0; + + for (i = 0; i < 0x20; i++) { + g_print("\t%u,\t/* %u */\n", 0, count); + count++; + } + + gchar * c = data; + while (*c) { + if (*c != '\n') { + gunichar u = g_utf8_get_char(c); + g_print("\t%u,\t/* %u */\n", u, count); + count++; + } + c = g_utf8_next_char(c); + } + + g_printerr("Total elements = %d\n", count); + + return 0; +} diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..d3af7d2 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +cfmradio (0.1) unstable; urgency=low + + * Initial Release. + + -- Javier S. Pedro <maemo@javispedro.com> Sat, 30 Oct 2010 22:28:23 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..21bb97e --- /dev/null +++ b/debian/control @@ -0,0 +1,79 @@ +Source: cfmradio +Section: user/multimedia +Priority: extra +Maintainer: Javier S. Pedro <maemo@javispedro.com> +Build-Depends: debhelper (>= 5), libosso-dev, libhildon1-dev, libasound2-dev, + libpulse-dev, maemo-launcher-dev +Standards-Version: 3.7.2 + +Package: cfmradio +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, n900-fmrx-enabler +Description: FM radio player + Listen to FM radio from your N900 +XB-Description-es_ES: Reproductor de radio FM + Escucha radio FM desde tu N900 +XSBC-Maemo-Display-Name: Radio +XB-Maemo-Icon-26: + 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 + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..69776ee --- /dev/null +++ b/debian/copyright @@ -0,0 +1,26 @@ +This is cfmradio, written and maintained by Javier S. Pedro <maemo@javispedro.com> +on Sat, 30 Oct 2010 22:28:23 +0200. + +The original source can always be found at: + ftp://ftp.debian.org/dists/unstable/main/source/ + +Copyright Holder: Javier S. Pedro + +License: + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..53552b7 --- /dev/null +++ b/debian/dirs @@ -0,0 +1,4 @@ +usr/bin +usr/share/icons/hicolor/scalable/hildon +usr/share/dbus-1/services +usr/share/applications/hildon diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/debian/docs diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..ef96488 --- /dev/null +++ b/debian/rules @@ -0,0 +1,70 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +CFLAGS = -Wall -g + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -Os +endif + +build: build-stamp + +build-stamp: + dh_testdir + + $(MAKE) + + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + + -$(MAKE) clean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + $(MAKE) DESTDIR=$(CURDIR)/debian/cfmradio install + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_installexamples + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install diff --git a/n900-fmrx-enabler.h b/n900-fmrx-enabler.h new file mode 100644 index 0000000..cd8480b --- /dev/null +++ b/n900-fmrx-enabler.h @@ -0,0 +1,65 @@ +/* Generated by dbus-binding-tool; do not edit! */ + +#include <glib.h> +#include <dbus/dbus-glib.h> + +G_BEGIN_DECLS + +#ifndef _DBUS_GLIB_ASYNC_DATA_FREE +#define _DBUS_GLIB_ASYNC_DATA_FREE +static +#ifdef G_HAVE_INLINE +inline +#endif +void +_dbus_glib_async_data_free (gpointer stuff) +{ + g_slice_free (DBusGAsyncData, stuff); +} +#endif + +#ifndef DBUS_GLIB_CLIENT_WRAPPERS_de_pycage_FMRXEnabler +#define DBUS_GLIB_CLIENT_WRAPPERS_de_pycage_FMRXEnabler + +static +#ifdef G_HAVE_INLINE +inline +#endif +gboolean +de_pycage_FMRXEnabler_request (DBusGProxy *proxy, gint* OUT_result, char ** OUT_device, GError **error) + +{ + return dbus_g_proxy_call (proxy, "request", error, G_TYPE_INVALID, G_TYPE_INT, OUT_result, G_TYPE_STRING, OUT_device, G_TYPE_INVALID); +} + +typedef void (*de_pycage_FMRXEnabler_request_reply) (DBusGProxy *proxy, gint OUT_result, char * OUT_device, GError *error, gpointer userdata); + +static void +de_pycage_FMRXEnabler_request_async_callback (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data) +{ + DBusGAsyncData *data = (DBusGAsyncData*) user_data; + GError *error = NULL; + gint OUT_result; + char * OUT_device; + dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INT, &OUT_result, G_TYPE_STRING, &OUT_device, G_TYPE_INVALID); + (*(de_pycage_FMRXEnabler_request_reply)data->cb) (proxy, OUT_result, OUT_device, error, data->userdata); + return; +} + +static +#ifdef G_HAVE_INLINE +inline +#endif +DBusGProxyCall* +de_pycage_FMRXEnabler_request_async (DBusGProxy *proxy, de_pycage_FMRXEnabler_request_reply callback, gpointer userdata) + +{ + DBusGAsyncData *stuff; + stuff = g_slice_new (DBusGAsyncData); + stuff->cb = G_CALLBACK (callback); + stuff->userdata = userdata; + return dbus_g_proxy_begin_call (proxy, "request", de_pycage_FMRXEnabler_request_async_callback, stuff, _dbus_glib_async_data_free, G_TYPE_INVALID); +} +#endif /* defined DBUS_GLIB_CLIENT_WRAPPERS_de_pycage_FMRXEnabler */ + +G_END_DECLS diff --git a/n900-fmrx-enabler.xml b/n900-fmrx-enabler.xml new file mode 100644 index 0000000..3fb6593 --- /dev/null +++ b/n900-fmrx-enabler.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<!-- +D-Bus service bindings +Copyright (C) 2009 Martin Grimme <martin.grimme@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +--> + +<node name="/de/pycage/FMRXEnabler"> + + <interface name="de.pycage.FMRXEnabler"> + + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="FMRXEnabler"/> + + <method name="request"> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" + value="dbus_fmrx_enabler_request"/> + <arg type="i" name="result" direction="out"/> + <arg type="s" name="device" direction="out"/> + </method> + + </interface> + +</node> + diff --git a/preset_list.c b/preset_list.c new file mode 100644 index 0000000..0fef49b --- /dev/null +++ b/preset_list.c @@ -0,0 +1,149 @@ +/* + * GPL 2 + */ + +#include <gtk/gtk.h> +#include <hildon/hildon.h> + +#include "presets.h" +#include "preset_list.h" + +G_DEFINE_TYPE(CFmPresetList, cfm_preset_list, HILDON_TYPE_STACKABLE_WINDOW); + +#define CFM_PRESET_LIST_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CFM_TYPE_PRESET_LIST, CFmPresetListPrivate)) + +#define ABS_RANGE_LOW 60000000 +#define ABS_RANGE_HIGH 140000000 + +struct _CFmPresetListPrivate { + HildonTouchSelector *sel; + HildonTouchSelectorColumn *col; +}; + +enum { + PROP_0, + PROP_MODEL, + PROP_FREQUENCY, + PROP_LAST +}; + +enum { + SIGNAL_0, + SIGNAL_LAST +}; + +static GParamSpec *properties[PROP_LAST]; +static guint signals[SIGNAL_LAST]; + +static void cfm_preset_list_selection_changed(HildonTouchSelector *selector, + gint column, gpointer user_data) +{ + CFmPresetList *self = CFM_PRESET_LIST(user_data); + + g_object_notify(G_OBJECT(self), "frequency"); +} + +static void cfm_preset_list_set_property(GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + CFmPresetList *self = CFM_PRESET_LIST(object); + CFmPresetListPrivate *priv = self->priv; + switch (property_id) { + case PROP_MODEL: + hildon_touch_selector_set_model(priv->sel, 0, g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void cfm_preset_list_get_property(GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + CFmPresetList *self = CFM_PRESET_LIST(object); + CFmPresetListPrivate *priv = self->priv; + switch (property_id) { + case PROP_MODEL: + g_value_set_object(value, hildon_touch_selector_get_model(priv->sel, 0)); + break; + case PROP_FREQUENCY: { + GtkTreeIter iter; + if (hildon_touch_selector_get_selected(priv->sel, 0, &iter)) { + gulong freq; + gtk_tree_model_get(hildon_touch_selector_get_model(priv->sel, 0), + &iter, 0, &freq, -1); + g_value_set_ulong(value, freq); + } + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void cfm_preset_list_init(CFmPresetList *self) +{ + CFmPresetListPrivate *priv; + + self->priv = priv = CFM_PRESET_LIST_GET_PRIVATE(self); + + /* Empty model for now */ + GtkTreeModel *model = GTK_TREE_MODEL(gtk_list_store_new(2, G_TYPE_FLOAT, G_TYPE_STRING)); + + gtk_window_set_title(GTK_WINDOW(self), "Presets"); + hildon_gtk_window_set_portrait_flags(GTK_WINDOW(self), + HILDON_PORTRAIT_MODE_SUPPORT); + + priv->sel = HILDON_TOUCH_SELECTOR(hildon_touch_selector_new()); + priv->col = hildon_touch_selector_append_text_column(priv->sel, model, FALSE); + hildon_touch_selector_column_set_text_column(priv->col, 1); + + gtk_container_add(GTK_CONTAINER(self), GTK_WIDGET(priv->sel)); + + g_signal_connect(G_OBJECT(priv->sel), "changed", + G_CALLBACK(cfm_preset_list_selection_changed), self); + + g_object_unref(model); +} + +static void cfm_preset_list_class_init(CFmPresetListClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GParamSpec *param_spec; + + gobject_class->set_property = cfm_preset_list_set_property; + gobject_class->get_property = cfm_preset_list_get_property; + + g_type_class_add_private(klass, sizeof(CFmPresetListPrivate)); + + param_spec = g_param_spec_object("model", + "GtkTreeModel to use", + "This is the model where contents will be read from", + GTK_TYPE_TREE_MODEL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_MODEL] = param_spec; + g_object_class_install_property(gobject_class, PROP_MODEL, param_spec); + + param_spec = g_param_spec_ulong("frequency", + "Frequency to tune (Hz)", + "This is the frequency to tune, in Hz", + ABS_RANGE_LOW, ABS_RANGE_HIGH, ABS_RANGE_LOW, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_FREQUENCY] = param_spec; + g_object_class_install_property(gobject_class, PROP_FREQUENCY, param_spec); +} + +CFmPresetList* cfm_preset_list_new() +{ + return g_object_new(CFM_TYPE_PRESET_LIST, NULL); +} + +void cfm_preset_list_show_for(CFmPresetList *self, CFmPresets *presets) +{ + g_object_set(self, "model", cfm_presets_get_all(presets), NULL); + + gtk_widget_show_all(GTK_WIDGET(self)); +} + diff --git a/preset_list.h b/preset_list.h new file mode 100644 index 0000000..9930242 --- /dev/null +++ b/preset_list.h @@ -0,0 +1,37 @@ +/* + * GPL 2 + */ + +#ifndef _CFM_PRESET_LIST_H_ +#define _CFM_PRESET_LIST_H_ + +#include <hildon/hildon.h> +#include "presets.h" + +#define CFM_TYPE_PRESET_LIST (cfm_preset_list_get_type ()) +#define CFM_PRESET_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CFM_TYPE_PRESET_LIST, CFmPresetList)) +#define CFM_IS_PRESET_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CFM_TYPE_PRESET_LIST)) +#define CFM_PRESET_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CFM_TYPE_PRESET_LIST, CFmPresetListClass)) +#define CFM_IS_PRESET_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CFM_TYPE_PRESET_LIST)) +#define CFM_PRESET_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CFM_TYPE_PRESET_LIST, CFmPresetListClass)) + +typedef struct _CFmPresetList CFmPresetList; +typedef struct _CFmPresetListPrivate CFmPresetListPrivate; +typedef struct _CFmPresetListClass CFmPresetListClass; + +struct _CFmPresetList +{ + HildonStackableWindow parent; + CFmPresetListPrivate *priv; +}; + +struct _CFmPresetListClass +{ + HildonStackableWindowClass parent; +}; + +GType cfm_preset_list_get_type (void); +CFmPresetList* cfm_preset_list_new(); +void cfm_preset_list_show_for(CFmPresetList *self, CFmPresets *presets); + +#endif /* _CFM_PRESET_LIST_H_ */ diff --git a/presets.c b/presets.c new file mode 100644 index 0000000..f35901a --- /dev/null +++ b/presets.c @@ -0,0 +1,344 @@ +/* + * GPL 2 + */ + +#include <glib.h> +#include <glib-object.h> +#include <gconf/gconf-client.h> + +#include "presets.h" + +G_DEFINE_TYPE(CFmPresets, cfm_presets, G_TYPE_OBJECT); + +#define CFM_PRESETS_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CFM_TYPE_PRESETS, CFmPresetsPrivate)) + +#define GCONF_KEY_BUFFER_LEN 1024 +#define GCONF_PATH "/apps/maemo/cfmradio/presets" + +struct _CFmPresetsPrivate { + GConfClient *gconf; + gchar *name; + gchar *gconf_dir; + guint gconf_notify; + GHashTable *t; +}; + +enum { + PROP_0, + PROP_NAME, + PROP_LAST +}; + +enum { + SIGNAL_0, + SIGNAL_LAST +}; + +static GParamSpec *properties[PROP_LAST]; +static guint signals[SIGNAL_LAST]; + +static inline gfloat freq_to_float(gulong f) +{ + return f / 1000000.0; +} + +static inline gulong float_to_freq(gfloat f) +{ + return f * 1000000.0; +} + +static inline gfloat pointer_to_float(gpointer p) +{ + union { + gfloat f; + gpointer p; + } u; + u.p = p; + return u.f; +} + +static inline gpointer float_to_pointer(gfloat f) +{ + union { + gfloat f; + gpointer p; + } u; + u.f = f; + return u.p; +} + +static inline gpointer freq_to_pointer(gulong f) +{ + return float_to_pointer(freq_to_float(f)); +} + +static inline gulong pointer_to_freq(gpointer p) +{ + return float_to_freq(pointer_to_float(p)); +} + +static void func_gconf_entry_free(gpointer data, gpointer user_data) +{ + GConfEntry *entry = (GConfEntry*) data; + gconf_entry_free(entry); +} + +static void cfm_presets_load(CFmPresets *self) +{ + CFmPresetsPrivate *priv = self->priv; + + g_debug("Loading presets from %s\n", priv->gconf_dir); + + g_hash_table_remove_all(priv->t); + + GSList *i, *l = gconf_client_all_entries(priv->gconf, priv->gconf_dir, NULL); + for (i = l; i; i = g_slist_next(i)) { + GConfEntry *entry = (GConfEntry*) i->data; + const gchar *basename = g_basename(gconf_entry_get_key(entry)); + gfloat freq = g_ascii_strtod(basename, NULL); + const gchar *name = gconf_value_get_string(gconf_entry_get_value(entry)); + g_hash_table_insert(priv->t, float_to_pointer(freq), g_strdup(name)); + } + + g_slist_foreach(l, func_gconf_entry_free, NULL); + g_slist_free(l); +} + +static void cfm_presets_gconf_notify(GConfClient *gconf, guint cnxn_id, + GConfEntry *entry, gpointer user_data) +{ + CFmPresets *self = CFM_PRESETS(user_data); + CFmPresetsPrivate *priv = self->priv; + + if (!entry) { + return; + } + + const gchar *basename = g_basename(gconf_entry_get_key(entry)); + gfloat freq = g_ascii_strtod(basename, NULL); + GConfValue *value = gconf_entry_get_value(entry); + + if (value) { + const gchar *name = gconf_value_get_string(gconf_entry_get_value(entry)); + g_debug("Preset '%s' changed to '%s'\n", basename, name); + g_hash_table_insert(priv->t, float_to_pointer(freq), g_strdup(name)); + } else { + g_debug("Preset '%s' removed\n", basename); + g_hash_table_remove(priv->t, float_to_pointer(freq)); + } +} + +static void cfm_presets_set_property(GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + CFmPresets *self = CFM_PRESETS(object); + switch (property_id) { + case PROP_NAME: + g_free(self->priv->name); + self->priv->name = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void cfm_presets_get_property(GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + CFmPresets *self = CFM_PRESETS(object); + switch (property_id) { + case PROP_NAME: + g_debug("Prop name is now set\n"); + g_value_set_string(value, self->priv->name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void cfm_presets_init(CFmPresets *self) +{ + CFmPresetsPrivate *priv; + + self->priv = priv = CFM_PRESETS_GET_PRIVATE(self); + + priv->gconf = gconf_client_get_default(); + priv->t = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); +} + +static GObject * cfm_presets_constructor(GType gtype, guint n_properties, + GObjectConstructParam *properties) +{ + GObject *object = G_OBJECT_CLASS(cfm_presets_parent_class)->constructor( + gtype, n_properties, properties); + CFmPresets *self = CFM_PRESETS(object); + CFmPresetsPrivate *priv = self->priv; + + priv->gconf_dir = g_strdup_printf("%s/%s", GCONF_PATH, priv->name); + + gconf_client_add_dir(priv->gconf, priv->gconf_dir, GCONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + priv->gconf_notify = gconf_client_notify_add(priv->gconf, priv->gconf_dir, + cfm_presets_gconf_notify, self, NULL, NULL); + + cfm_presets_load(self); + + return object; +} + +static void cfm_presets_dispose(GObject *object) +{ + CFmPresets *self = CFM_PRESETS(object); + CFmPresetsPrivate *priv = self->priv; + + if (priv->gconf && priv->gconf_notify) { + gconf_client_notify_remove(priv->gconf, priv->gconf_notify); + } + if (priv->gconf && priv->gconf_dir) { + gconf_client_remove_dir(priv->gconf, priv->gconf_dir, NULL); + } + if (priv->gconf_dir) { + g_free(priv->gconf_dir); + priv->gconf_dir = NULL; + } + if (priv->gconf) { + g_object_unref(priv->gconf); + priv->gconf = NULL; + } +} + +static void cfm_presets_finalize(GObject *object) +{ + CFmPresets *self = CFM_PRESETS(object); + CFmPresetsPrivate *priv = self->priv; + + g_hash_table_destroy(priv->t); + g_free(priv->name); +} + +static void cfm_presets_class_init(CFmPresetsClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GParamSpec *param_spec; + + g_type_class_add_private (klass, sizeof(CFmPresetsPrivate)); + + gobject_class->constructor = cfm_presets_constructor; + gobject_class->set_property = cfm_presets_set_property; + gobject_class->get_property = cfm_presets_get_property; + gobject_class->dispose = cfm_presets_dispose; + gobject_class->finalize = cfm_presets_finalize; + + param_spec = g_param_spec_string("name", + "Preset set name", + "This is the name for this set of presets", + "default", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + properties[PROP_NAME] = param_spec; + g_object_class_install_property(gobject_class, PROP_NAME, param_spec); +} + +static gpointer cfm_presets_build_default(gpointer data) +{ + return g_object_new(CFM_TYPE_PRESETS, NULL); +} + +CFmPresets* cfm_presets_get_default() +{ + static GOnce once = G_ONCE_INIT; + g_once(&once, cfm_presets_build_default, NULL); + + return CFM_PRESETS(g_object_ref(G_OBJECT(once.retval))); +} + +CFmPresets* cfm_presets_get_for_name(const char * name) +{ + return g_object_new(CFM_TYPE_PRESETS, "name", name, NULL); +} + +void cfm_presets_set_preset(CFmPresets *self, gulong freq, const gchar *name) +{ + CFmPresetsPrivate *priv = self->priv; + GError *error = NULL; + gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; + gchar *key = g_strdup_printf("%s/%s", priv->gconf_dir, + g_ascii_formatd(buf, sizeof(buf), "%.1f", freq_to_float(freq))); + if (!gconf_client_set_string(priv->gconf, key, name, &error)) { + g_warning("Failed to store preset '%s' ('%s'): %s\n", key, name, + error->message); + } + g_free(key); +} + +void cfm_presets_remove_preset(CFmPresets *self, gulong freq) +{ + CFmPresetsPrivate *priv = self->priv; + GError *error = NULL; + gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; + gchar *key = g_strdup_printf("%s/%s", priv->gconf_dir, + g_ascii_formatd(buf, sizeof(buf), "%.1f", freq_to_float(freq))); + if (!gconf_client_unset(priv->gconf, key, &error)) { + g_warning("Failed to remove preset '%s': %s\n", key, error->message); + } + g_free(key); +} + +gboolean cfm_presets_is_preset(CFmPresets *self, gulong freq) +{ + CFmPresetsPrivate *priv = self->priv; + + return g_hash_table_lookup(priv->t, freq_to_pointer(freq)) ? TRUE : FALSE; +} + +const gchar * cfm_presets_get_preset(CFmPresets *self, gulong freq) +{ + CFmPresetsPrivate *priv = self->priv; + + gpointer found = g_hash_table_lookup(priv->t, freq_to_pointer(freq)); + if (found) { + return (const gchar *) found; + } else { + return NULL; + } + + return NULL; +} + +static void preset_to_list_store(gpointer key, gpointer value, gpointer user_data) +{ + GtkListStore *l = GTK_LIST_STORE(user_data); + GtkTreeIter iter; + + gulong freq = pointer_to_freq(key); + gtk_list_store_insert_with_values(l, &iter, 0, 0, freq, 1, value, -1); +} + +static gint compare_freq(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, + gpointer user_data) +{ + gulong freq_a, freq_b; + gtk_tree_model_get(model, a, 0, &freq_a, -1); + gtk_tree_model_get(model, b, 0, &freq_b, -1); + + if (freq_a > freq_b) return 1; + else if (freq_a < freq_b) return -1; + else return 0; +} + +GtkListStore* cfm_presets_get_all(CFmPresets *self) +{ + CFmPresetsPrivate *priv = self->priv; + GtkListStore *l = gtk_list_store_new(2, G_TYPE_ULONG, G_TYPE_STRING); + g_hash_table_foreach(priv->t, preset_to_list_store, l); + + gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(l), 0, compare_freq, + NULL, NULL); + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(l), 0, + GTK_SORT_ASCENDING); + + return l; +} + diff --git a/presets.h b/presets.h new file mode 100644 index 0000000..88c910d --- /dev/null +++ b/presets.h @@ -0,0 +1,45 @@ +/* + * GPL 2 + */ + +#ifndef __CFM_PRESETS_H__ +#define __CFM_PRESETS_H__ + +#include <glib-object.h> +#include <gtk/gtk.h> + +#define CFM_TYPE_PRESETS (cfm_presets_get_type ()) +#define CFM_PRESETS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CFM_TYPE_PRESETS, CFmPresets)) +#define CFM_IS_PRESETS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CFM_TYPE_PRESETS)) +#define CFM_PRESETS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CFM_TYPE_PRESETS, CFmPresetsClass)) +#define CFM_IS_PRESETS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CFM_TYPE_PRESETS)) +#define CFM_PRESETS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CFM_TYPE_PRESETS, CFmPresetsClass)) + +typedef struct _CFmPresets CFmPresets; +typedef struct _CFmPresetsPrivate CFmPresetsPrivate; +typedef struct _CFmPresetsClass CFmPresetsClass; + +struct _CFmPresets +{ + GObject parent_instance; + CFmPresetsPrivate *priv; +}; + +struct _CFmPresetsClass +{ + GObjectClass parent_class; +}; + +GType cfm_presets_get_type(void); +CFmPresets* cfm_presets_get_default(); +CFmPresets* cfm_presets_get_for_name(const gchar *name); + +void cfm_presets_set_preset(CFmPresets *self, gulong freq, const gchar *name); +void cfm_presets_remove_preset(CFmPresets *self, gulong freq); +gboolean cfm_presets_is_preset(CFmPresets *self, gulong freq); +const gchar * cfm_presets_get_preset(CFmPresets *self, gulong freq); + +GtkListStore* cfm_presets_get_all(CFmPresets *self); + +#endif /* __CFM_PRESETS_H__ */ + @@ -0,0 +1,666 @@ +/* + * GPL 2 + */ + +#include <stdio.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <fcntl.h> + +#include <alsa/asoundlib.h> +#include <linux/videodev2.h> +#include <dbus/dbus-glib.h> +#include <pulse/glib-mainloop.h> +#include <pulse/error.h> +#include <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/xmalloc.h> + +#include "radio.h" +#include "types.h" +#include "n900-fmrx-enabler.h" +#include "rds.h" + +G_DEFINE_TYPE(CFmRadio, cfm_radio, G_TYPE_OBJECT); + +#define CFM_RADIO_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CFM_TYPE_RADIO, CFmRadioPrivate)) + +#define ABS_RANGE_LOW 60000000 +#define ABS_RANGE_HIGH 140000000 +#define MIN_BUFFER_SIZE (4096*4) + +#define FMRX_SERVICE_NAME "de.pycage.FMRXEnabler" +#define FMRX_OBJECT_PATH "/de/pycage/FMRXEnabler" +#define FMRX_INTERFACE "de.pycage.FMRXEnabler" +#define FMRX_KEEPALIVE_INTERVAL 20 + +#define SYSFS_NODE_PATH "/sys/class/i2c-adapter/i2c-3/3-0022" + +#define MIXER_NAME "hw:0" + +static void cfm_radio_turn_on(CFmRadio *self); +static void cfm_radio_turn_off(CFmRadio *self); + +struct _CFmRadioPrivate { + int fd; + + CFmRadioOutput output; + + gboolean precise_tuner; + gulong range_low, range_high; + + DBusGProxy *enabler; + guint enabler_timer; + + pa_glib_mainloop *pa_loop; + pa_context *pa_ctx; + pa_stream *so, *si; + + snd_hctl_t *mixer; +}; + +enum { + PROP_0, + PROP_OUTPUT, + PROP_FREQUENCY, + PROP_RANGE_LOW, + PROP_RANGE_HIGH, + PROP_SIGNAL, + PROP_RDS_PI, + PROP_RDS_PS, + PROP_RDS_RT, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +static void cfm_radio_tuner_power(CFmRadio *self, gboolean enable) +{ + CFmRadioPrivate *priv = self->priv; + struct v4l2_control vctrl; + int res; + + if (priv->fd == -1) return; + + vctrl.id = V4L2_CID_AUDIO_MUTE; + vctrl.value = enable ? 0 : 1; + res = ioctl(priv->fd, VIDIOC_S_CTRL, &vctrl); + if (res < 0) { + perror("VIDIOC_S_CTRL"); + } +} + +static void cfm_radio_mixer_set_enum_value(CFmRadio *self, const char * name, const char * value) +{ + CFmRadioPrivate *priv = self->priv; + int err; + + snd_ctl_elem_id_t *id; + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(id, name); + + snd_hctl_elem_t *elem = snd_hctl_find_elem(priv->mixer, id); + g_return_if_fail(elem != NULL); + + snd_ctl_elem_info_t *info; + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_info_set_id(info, id); + err = snd_hctl_elem_info(elem, info); + g_return_if_fail(err == 0); + + long value_idx = -1; + int i, items = snd_ctl_elem_info_get_items(info); + for (i = 0; i < items; i++) { + snd_ctl_elem_info_set_item(info, i); + err = snd_hctl_elem_info(elem, info); + g_return_if_fail(err == 0); + if (strcmp(snd_ctl_elem_info_get_item_name(info), value) == 0) { + value_idx = i; + break; + } + } + g_return_if_fail(value_idx >= 0); + + snd_ctl_elem_value_t *control; + snd_ctl_elem_value_alloca(&control); + snd_ctl_elem_value_set_id(control, id); + + items = snd_ctl_elem_info_get_count(info); + for (i = 0; i < items; i++) { + snd_ctl_elem_value_set_enumerated(control, i, value_idx); + } + + err = snd_hctl_elem_write(elem, control); + g_return_if_fail(err == 0); +} + +static void cfm_radio_mixer_set_bool_value(CFmRadio *self, const char * name, gboolean value) +{ + CFmRadioPrivate *priv = self->priv; + int err; + + snd_ctl_elem_id_t *id; + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(id, name); + + snd_hctl_elem_t *elem = snd_hctl_find_elem(priv->mixer, id); + g_return_if_fail(elem != NULL); + + snd_ctl_elem_info_t *info; + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_info_set_id(info, id); + err = snd_hctl_elem_info(elem, info); + g_return_if_fail(err == 0); + + snd_ctl_elem_value_t *control; + snd_ctl_elem_value_alloca(&control); + snd_ctl_elem_value_set_id(control, id); + + int i, items = snd_ctl_elem_info_get_count(info); + for (i = 0; i < items; i++) { + snd_ctl_elem_value_set_boolean(control, i, value); + } + + err = snd_hctl_elem_write(elem, control); + g_return_if_fail(err == 0); +} + +static void cfm_radio_mixer_enable(CFmRadio *self, gboolean enable) +{ + if (enable) { + cfm_radio_mixer_set_enum_value(self, "Input Select", "ADC"); + cfm_radio_mixer_set_bool_value(self, "PGA Capture Switch", TRUE); + cfm_radio_mixer_set_bool_value(self, "Left PGA Mixer Line2L Switch", TRUE); + cfm_radio_mixer_set_bool_value(self, "Right PGA Mixer Line2R Switch", TRUE); + } else { + cfm_radio_mixer_set_enum_value(self, "Input Select", "Digital Mic"); + cfm_radio_mixer_set_bool_value(self, "PGA Capture Switch", FALSE); + cfm_radio_mixer_set_bool_value(self, "Left PGA Mixer Line2L Switch", FALSE); + cfm_radio_mixer_set_bool_value(self, "Right PGA Mixer Line2R Switch", FALSE); + } +} + +static void cfm_radio_init_tuner(CFmRadio *self, const gchar * device) +{ + CFmRadioPrivate *priv = self->priv; + struct v4l2_tuner tuner = { 0 }; + int res; + + priv->fd = open(device, O_RDONLY); + if (priv->fd == -1) { + perror("Open radio tuner"); + } + + tuner.index = 0; + res = ioctl(priv->fd, VIDIOC_G_TUNER, &tuner); + if (res < 0) { + perror("VIDIOC_G_TUNER"); + return; + } + + if (tuner.type != V4L2_TUNER_RADIO) { + g_warning("Not a radio tuner\n"); + return; + } + + priv->precise_tuner = (tuner.capability & V4L2_TUNER_CAP_LOW) ? + TRUE : FALSE; + + if (priv->precise_tuner) { + priv->range_low = tuner.rangelow * 62.5f; + priv->range_high = tuner.rangehigh * 62.5f; + } else { + priv->range_low = tuner.rangelow * 62500; + priv->range_high = tuner.rangehigh * 62500; + } + + g_debug("Tuner detected (from %lu to %lu)\n", priv->range_low, priv->range_high); + + cfm_radio_tuner_power(self, TRUE); + + g_debug("Tuner powered!\n"); + + g_object_notify(G_OBJECT(self), "range-low"); + g_object_notify(G_OBJECT(self), "range-high"); + g_object_notify(G_OBJECT(self), "frequency"); +} + +static void cfm_radio_fmrx_request_cb(DBusGProxy *proxy, gint result, char * device, GError *error, gpointer userdata) +{ + CFmRadio *self = CFM_RADIO(userdata); + CFmRadioPrivate *priv = self->priv; + + if (error) { + g_warning("D-Bus error while contacting fmrx enabler: %s\n", + error->message); + g_error_free(error); + } else if (result != 0) { + g_warning("Ungranted acess to fmrx device (%d)\n", result); + } else if (priv->fd != -1) { + g_debug("Renewed access to device\n"); + } else { + g_debug("Granted access to device: %s\n", device); + cfm_radio_init_tuner(self, device); + /* TODO: Possibly initialize more stuff. */ + } + + if (device) { + g_free(device); + } +} + +static void cfm_radio_fmrx_request(CFmRadio *self) +{ + CFmRadioPrivate *priv = self->priv; + g_return_if_fail(priv->enabler); + + de_pycage_FMRXEnabler_request_async(priv->enabler, + cfm_radio_fmrx_request_cb, self); +} + +static gboolean cfm_radio_fmrx_keepalive(gpointer data) +{ + CFmRadio *self = CFM_RADIO(data); + CFmRadioPrivate *priv = self->priv; + + if (!priv->enabler) { + priv->enabler_timer = 0; + return FALSE; + } + + cfm_radio_fmrx_request(self); + + return TRUE; +} + +static void cfm_radio_ctx_state_change(pa_context *c, void *userdata) +{ + CFmRadio *self = CFM_RADIO(userdata); + CFmRadioPrivate *priv = self->priv; + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + if (priv->output != CFM_RADIO_OUTPUT_MUTE && (!priv->so || !priv->si)) { + cfm_radio_turn_on(self); + } + break; + case PA_CONTEXT_FAILED: + cfm_radio_turn_off(self); + g_warning("Pulseaudio connection failed\n"); + break; + default: + break; + } +} + +static void cfm_radio_si_request(pa_stream *p, size_t nbytes, void *userdata) +{ + CFmRadio *self = CFM_RADIO(userdata); + CFmRadioPrivate *priv = self->priv; + + if (nbytes < MIN_BUFFER_SIZE) + return; + + void * in; + size_t in_nbytes; + int res = pa_stream_peek(priv->si, (const void **) &in, &in_nbytes); + if (res != 0) { + g_warning("Failed to read from input stream: %s\n", pa_strerror(res)); + return; + } + g_warn_if_fail(in); + + res = pa_stream_write(priv->so, in, in_nbytes, NULL, 0, PA_SEEK_RELATIVE); + if (res != 0) { + g_warning("Failed to write to output stream: %s\n", pa_strerror(res)); + return; + } + + pa_stream_drop(priv->si); +} + +static void cfm_radio_turn_on(CFmRadio *self) +{ + CFmRadioPrivate *priv = self->priv; + /* A good default for the N900. */ + pa_sample_spec spec = { + .format = PA_SAMPLE_S16LE, + .rate = 48000, + .channels = 2 + }; + int res; + g_warn_if_fail(priv->pa_ctx); + + priv->si = pa_stream_new(priv->pa_ctx, "FMRadio input", &spec, NULL); + priv->so = pa_stream_new(priv->pa_ctx, "FMRadio output", &spec, NULL); + + pa_stream_set_read_callback(priv->si, cfm_radio_si_request, self); + + res = pa_stream_connect_playback(priv->so, NULL, NULL, 0, NULL, NULL); + if (res != 0) { + g_warning("Failed to connect output stream: %s\n", pa_strerror(res)); + } + res = pa_stream_connect_record(priv->si, NULL, NULL, 0); + if (res != 0) { + g_warning("Failed to connect input stream: %s\n", pa_strerror(res)); + } + + cfm_radio_mixer_enable(self, TRUE); + + g_debug("Turned on\n"); +} + +static void cfm_radio_turn_off(CFmRadio *self) +{ + CFmRadioPrivate *priv = self->priv; + if (priv->so) { + pa_stream_disconnect(priv->so); + pa_stream_unref(priv->so); + priv->so = NULL; + } + if (priv->si) { + pa_stream_disconnect(priv->si); + pa_stream_unref(priv->si); + priv->si = NULL; + } + cfm_radio_mixer_enable(self, FALSE); + g_debug("Turned off\n"); +} + +static void cfm_radio_init(CFmRadio *self) +{ + CFmRadioPrivate *priv; + GError *error = NULL; + int res; + + self->priv = priv = CFM_RADIO_GET_PRIVATE(self); + priv->fd = -1; + + DBusGConnection *conn = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); + if (!conn) { + g_warning("Failed to get system bus: %s\n", error->message); + g_error_free(error); + } + + priv->enabler = dbus_g_proxy_new_for_name_owner(conn, FMRX_SERVICE_NAME, + FMRX_OBJECT_PATH, FMRX_INTERFACE, &error); + if (!priv->enabler) { + g_warning("Failed to connect to fmrx enabler service: %s\n", + error->message); + g_error_free(error); + } + + cfm_radio_fmrx_request(self); + + priv->pa_loop = pa_glib_mainloop_new(NULL); + priv->pa_ctx = pa_context_new(pa_glib_mainloop_get_api(priv->pa_loop), + "FMRadio"); /* Note that the name is very important on Maemo. */ + pa_context_set_state_callback(priv->pa_ctx, cfm_radio_ctx_state_change, self); + res = pa_context_connect(priv->pa_ctx, NULL, 0, NULL); + g_warn_if_fail(res == 0); + + priv->enabler_timer = g_timeout_add_seconds(FMRX_KEEPALIVE_INTERVAL, + cfm_radio_fmrx_keepalive, self); + + res = snd_hctl_open(&priv->mixer, MIXER_NAME, 0); + if (res < 0) { + g_warning("Failed to open ALSA mixer res=%d\n", res); + } + res = snd_hctl_load(priv->mixer); + if (res < 0) { + g_warning("Failed to load ALSA hmixer elements res=%d\n", res); + } +} + +static void cfm_radio_set_output(CFmRadio *self, CFmRadioOutput mode) +{ + CFmRadioPrivate *priv = self->priv; + //CFmRadioOutput old_output = priv->output; + priv->output = mode; + if (mode == CFM_RADIO_OUTPUT_MUTE) { + cfm_radio_turn_off(self); + } else if (pa_context_get_state(priv->pa_ctx) == PA_CONTEXT_READY) { + cfm_radio_turn_on(self); + } +} + +static CFmRadioOutput cfm_radio_get_output(CFmRadio *self) +{ + CFmRadioPrivate *priv = self->priv; + return priv->output; +} + +static void cfm_radio_set_frequency(CFmRadio *self, gulong freq) +{ + CFmRadioPrivate *priv = self->priv; + struct v4l2_frequency t_freq = { 0 }; + g_return_if_fail(priv->fd != -1); + t_freq.tuner = 0; + t_freq.type = V4L2_TUNER_RADIO; + t_freq.frequency = priv->precise_tuner ? freq / 62.5f : freq / 62500; + int res = ioctl(priv->fd, VIDIOC_S_FREQUENCY, &t_freq); + g_warn_if_fail(res == 0); +} + +static gulong cfm_radio_get_frequency(CFmRadio *self) +{ + CFmRadioPrivate *priv = self->priv; + struct v4l2_frequency t_freq = { 0 }; + g_return_val_if_fail(priv->fd != -1, 0); + t_freq.tuner = 0; + int res = ioctl(priv->fd, VIDIOC_G_FREQUENCY, &t_freq); + g_return_val_if_fail(res == 0, 0); + return priv->precise_tuner ? t_freq.frequency * 62.5f : t_freq.frequency * 62500; +} + +static guint cfm_radio_get_signal(CFmRadio *self) +{ + CFmRadioPrivate *priv = self->priv; + struct v4l2_tuner tuner = { 0 }; + g_return_val_if_fail(priv->fd != -1, 0); + tuner.index = 0; + int res = ioctl(priv->fd, VIDIOC_G_TUNER, &tuner); + g_return_val_if_fail(res == 0, 0); + return tuner.signal; +} + +static gchar* cfm_radio_get_sysfs_key(CFmRadio *self, const gchar *key) +{ + GError *error = NULL; + gchar *file = g_strdup_printf("%s/%s", SYSFS_NODE_PATH, key); + gchar *r = NULL; + + if (!g_file_get_contents(file, &r, NULL, &error)) { + g_warning("Unable to read sysfs key %s: %s\n", key, error->message); + } + + g_free(file); + + return r; +} + +static gchar* cfm_radio_get_rds(CFmRadio *self, const gchar *key) +{ + gchar *v = cfm_radio_get_sysfs_key(self, key); + if (v) { + gchar * r = rds_decode(v); + g_free(v); + return r; + } else { + return NULL; + } +} + +static void cfm_radio_set_property(GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + CFmRadio *self = CFM_RADIO(object); + switch (property_id) { + case PROP_OUTPUT: + cfm_radio_set_output(self, g_value_get_enum(value)); + break; + case PROP_FREQUENCY: + cfm_radio_set_frequency(self, g_value_get_ulong(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void cfm_radio_get_property(GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + CFmRadio *self = CFM_RADIO(object); + switch (property_id) { + case PROP_OUTPUT: + g_value_set_enum(value, cfm_radio_get_output(self)); + break; + case PROP_FREQUENCY: + g_value_set_ulong(value, cfm_radio_get_frequency(self)); + break; + case PROP_RANGE_LOW: + g_value_set_ulong(value, self->priv->range_low); + break; + case PROP_RANGE_HIGH: + g_value_set_ulong(value, self->priv->range_high); + break; + case PROP_SIGNAL: + g_value_set_uint(value, cfm_radio_get_signal(self)); + break; + case PROP_RDS_PI: + g_value_take_string(value, cfm_radio_get_rds(self, "rds_pi")); + break; + case PROP_RDS_PS: + g_value_take_string(value, cfm_radio_get_rds(self, "rds_ps")); + break; + case PROP_RDS_RT: + g_value_take_string(value, cfm_radio_get_rds(self, "rds_rt")); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void cfm_radio_dispose(GObject *object) +{ + CFmRadio *self = CFM_RADIO(object); + CFmRadioPrivate *priv = self->priv; + if (priv->enabler_timer) { + g_source_remove(priv->enabler_timer); + priv->enabler_timer = 0; + } + cfm_radio_tuner_power(self, FALSE); + cfm_radio_turn_off(self); + if (priv->enabler) { + g_object_unref(priv->enabler); + priv->enabler = NULL; + } + if (priv->pa_ctx) { + pa_context_disconnect(priv->pa_ctx); + pa_context_unref(priv->pa_ctx); + priv->pa_ctx = NULL; + } + if (priv->pa_loop) { + pa_glib_mainloop_free(priv->pa_loop); + priv->pa_loop = NULL; + } +} + +static void cfm_radio_finalize(GObject *object) +{ + CFmRadio *self = CFM_RADIO(object); + CFmRadioPrivate *priv = self->priv; + if (priv->mixer) { + snd_hctl_close(priv->mixer); + priv->mixer = NULL; + } + if (priv->fd != -1) { + close(priv->fd); + priv->fd = -1; + } +} + +static void cfm_radio_class_init(CFmRadioClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GParamSpec *param_spec; + + gobject_class->set_property = cfm_radio_set_property; + gobject_class->get_property = cfm_radio_get_property; + gobject_class->dispose = cfm_radio_dispose; + gobject_class->finalize = cfm_radio_finalize; + + g_type_class_add_private (klass, sizeof(CFmRadioPrivate)); + + param_spec = g_param_spec_enum("output", "Output device", + "Audio output device", + CFM_TYPE_RADIO_OUTPUT, + CFM_RADIO_OUTPUT_MUTE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_OUTPUT] = param_spec; + g_object_class_install_property(gobject_class, PROP_OUTPUT, param_spec); + + param_spec = g_param_spec_ulong("frequency", + "Frequency to tune (Hz)", + "This is the frequency to tune, in Hz", + ABS_RANGE_LOW, ABS_RANGE_HIGH, ABS_RANGE_LOW, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_FREQUENCY] = param_spec; + g_object_class_install_property(gobject_class, PROP_FREQUENCY, param_spec); + + param_spec = g_param_spec_ulong("range-low", + "Min frequency range (Hz)", + "This is the lowest frequency that can be tuned, in Hz", + ABS_RANGE_LOW, ABS_RANGE_HIGH, ABS_RANGE_LOW, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_property(gobject_class, PROP_RANGE_LOW, param_spec); + properties[PROP_RANGE_LOW] = param_spec; + param_spec = g_param_spec_ulong("range-high", + "Max frequency range (Hz)", + "This is the highest frequency that can be tuned, in Hz", + ABS_RANGE_LOW, ABS_RANGE_HIGH, ABS_RANGE_HIGH, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + properties[PROP_RANGE_HIGH] = param_spec; + g_object_class_install_property(gobject_class, PROP_RANGE_HIGH, param_spec); + param_spec = g_param_spec_uint("signal", + "Signal strength", + "The signal strength if known, ranging from 0 (worse) to 65536 (best)", + 0, 65536, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + properties[PROP_SIGNAL] = param_spec; + g_object_class_install_property(gobject_class, PROP_SIGNAL, param_spec); + param_spec = g_param_spec_string("rds-pi", + "RDS Program Identification", + "Current station's code", + "", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + properties[PROP_RDS_PI] = param_spec; + g_object_class_install_property(gobject_class, PROP_RDS_PI, param_spec); + param_spec = g_param_spec_string("rds-ps", + "RDS Program Server", + "Current station's name", + "", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + properties[PROP_RDS_PS] = param_spec; + g_object_class_install_property(gobject_class, PROP_RDS_PS, param_spec); + param_spec = g_param_spec_string("rds-rt", + "RDS Radio Text", + "Current station's radio text", + "", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + properties[PROP_RDS_RT] = param_spec; + g_object_class_install_property(gobject_class, PROP_RDS_RT, param_spec); +} + +CFmRadio* cfm_radio_new() +{ + return g_object_new(CFM_TYPE_RADIO, NULL); +} + @@ -0,0 +1,37 @@ +/* + * GPL 2 + */ + +#ifndef __CFM_RADIO_H__ +#define __CFM_RADIO_H__ + +#include <glib-object.h> + +#define CFM_TYPE_RADIO (cfm_radio_get_type ()) +#define CFM_RADIO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CFM_TYPE_RADIO, CFmRadio)) +#define CFM_IS_RADIO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CFM_TYPE_RADIO)) +#define CFM_RADIO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CFM_TYPE_RADIO, CFmRadioClass)) +#define CFM_IS_RADIO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CFM_TYPE_RADIO)) +#define CFM_RADIO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CFM_TYPE_RADIO, CFmRadioClass)) + +typedef struct _CFmRadio CFmRadio; +typedef struct _CFmRadioPrivate CFmRadioPrivate; +typedef struct _CFmRadioClass CFmRadioClass; + +struct _CFmRadio +{ + GObject parent_instance; + CFmRadioPrivate *priv; +}; + +struct _CFmRadioClass +{ + GObjectClass parent_class; +}; + +GType cfm_radio_get_type (void); +CFmRadio* cfm_radio_new(); +void cfm_radio_free(CFmRadio* radio); + +#endif /* __CFM_RADIO_H__ */ + @@ -0,0 +1,281 @@ +#include <string.h> +#include <glib.h> + +#include "rds.h" + +/* This table comes from pyFMRadio source code. */ +static const gunichar rds_table[256] = { + 0, /* 0 */ + 0, /* 1 */ + 0, /* 2 */ + 0, /* 3 */ + 0, /* 4 */ + 0, /* 5 */ + 0, /* 6 */ + 0, /* 7 */ + 0, /* 8 */ + 0, /* 9 */ + 0, /* 10 */ + 0, /* 11 */ + 0, /* 12 */ + 0, /* 13 */ + 0, /* 14 */ + 0, /* 15 */ + 0, /* 16 */ + 0, /* 17 */ + 0, /* 18 */ + 0, /* 19 */ + 0, /* 20 */ + 0, /* 21 */ + 0, /* 22 */ + 0, /* 23 */ + 0, /* 24 */ + 0, /* 25 */ + 0, /* 26 */ + 0, /* 27 */ + 0, /* 28 */ + 0, /* 29 */ + 0, /* 30 */ + 0, /* 31 */ + 32, /* 32 */ + 33, /* 33 */ + 34, /* 34 */ + 35, /* 35 */ + 164, /* 36 */ + 37, /* 37 */ + 38, /* 38 */ + 39, /* 39 */ + 40, /* 40 */ + 41, /* 41 */ + 42, /* 42 */ + 43, /* 43 */ + 44, /* 44 */ + 45, /* 45 */ + 46, /* 46 */ + 47, /* 47 */ + 48, /* 48 */ + 49, /* 49 */ + 50, /* 50 */ + 51, /* 51 */ + 52, /* 52 */ + 53, /* 53 */ + 54, /* 54 */ + 55, /* 55 */ + 56, /* 56 */ + 57, /* 57 */ + 58, /* 58 */ + 59, /* 59 */ + 60, /* 60 */ + 61, /* 61 */ + 62, /* 62 */ + 63, /* 63 */ + 64, /* 64 */ + 65, /* 65 */ + 66, /* 66 */ + 67, /* 67 */ + 68, /* 68 */ + 69, /* 69 */ + 70, /* 70 */ + 71, /* 71 */ + 72, /* 72 */ + 73, /* 73 */ + 74, /* 74 */ + 75, /* 75 */ + 76, /* 76 */ + 77, /* 77 */ + 78, /* 78 */ + 79, /* 79 */ + 80, /* 80 */ + 81, /* 81 */ + 82, /* 82 */ + 83, /* 83 */ + 84, /* 84 */ + 85, /* 85 */ + 86, /* 86 */ + 87, /* 87 */ + 88, /* 88 */ + 89, /* 89 */ + 90, /* 90 */ + 91, /* 91 */ + 92, /* 92 */ + 93, /* 93 */ + 32, /* 94 */ + 32, /* 95 */ + 32, /* 96 */ + 97, /* 97 */ + 98, /* 98 */ + 99, /* 99 */ + 100, /* 100 */ + 101, /* 101 */ + 102, /* 102 */ + 103, /* 103 */ + 104, /* 104 */ + 105, /* 105 */ + 106, /* 106 */ + 107, /* 107 */ + 108, /* 108 */ + 109, /* 109 */ + 110, /* 110 */ + 111, /* 111 */ + 112, /* 112 */ + 113, /* 113 */ + 114, /* 114 */ + 115, /* 115 */ + 116, /* 116 */ + 117, /* 117 */ + 118, /* 118 */ + 119, /* 119 */ + 120, /* 120 */ + 121, /* 121 */ + 122, /* 122 */ + 123, /* 123 */ + 124, /* 124 */ + 125, /* 125 */ + 32, /* 126 */ + 32, /* 127 */ + 225, /* 128 */ + 224, /* 129 */ + 233, /* 130 */ + 232, /* 131 */ + 237, /* 132 */ + 236, /* 133 */ + 243, /* 134 */ + 242, /* 135 */ + 250, /* 136 */ + 249, /* 137 */ + 209, /* 138 */ + 199, /* 139 */ + 350, /* 140 */ + 223, /* 141 */ + 161, /* 142 */ + 306, /* 143 */ + 226, /* 144 */ + 228, /* 145 */ + 234, /* 146 */ + 235, /* 147 */ + 238, /* 148 */ + 239, /* 149 */ + 244, /* 150 */ + 246, /* 151 */ + 251, /* 152 */ + 252, /* 153 */ + 241, /* 154 */ + 231, /* 155 */ + 351, /* 156 */ + 287, /* 157 */ + 63, /* 158 */ + 307, /* 159 */ + 170, /* 160 */ + 945, /* 161 */ + 169, /* 162 */ + 8240, /* 163 */ + 486, /* 164 */ + 277, /* 165 */ + 328, /* 166 */ + 337, /* 167 */ + 960, /* 168 */ + 63, /* 169 */ + 163, /* 170 */ + 36, /* 171 */ + 8592, /* 172 */ + 8593, /* 173 */ + 8594, /* 174 */ + 8595, /* 175 */ + 186, /* 176 */ + 185, /* 177 */ + 178, /* 178 */ + 179, /* 179 */ + 177, /* 180 */ + 304, /* 181 */ + 324, /* 182 */ + 369, /* 183 */ + 956, /* 184 */ + 191, /* 185 */ + 247, /* 186 */ + 176, /* 187 */ + 188, /* 188 */ + 189, /* 189 */ + 190, /* 190 */ + 167, /* 191 */ + 193, /* 192 */ + 192, /* 193 */ + 201, /* 194 */ + 200, /* 195 */ + 205, /* 196 */ + 204, /* 197 */ + 211, /* 198 */ + 210, /* 199 */ + 218, /* 200 */ + 217, /* 201 */ + 344, /* 202 */ + 268, /* 203 */ + 352, /* 204 */ + 381, /* 205 */ + 272, /* 206 */ + 317, /* 207 */ + 194, /* 208 */ + 196, /* 209 */ + 202, /* 210 */ + 203, /* 211 */ + 206, /* 212 */ + 207, /* 213 */ + 212, /* 214 */ + 214, /* 215 */ + 219, /* 216 */ + 220, /* 217 */ + 345, /* 218 */ + 269, /* 219 */ + 353, /* 220 */ + 382, /* 221 */ + 271, /* 222 */ + 318, /* 223 */ + 195, /* 224 */ + 197, /* 225 */ + 198, /* 226 */ + 338, /* 227 */ + 375, /* 228 */ + 221, /* 229 */ + 213, /* 230 */ + 216, /* 231 */ + 254, /* 232 */ + 330, /* 233 */ + 340, /* 234 */ + 262, /* 235 */ + 346, /* 236 */ + 377, /* 237 */ + 63, /* 238 */ + 240, /* 239 */ + 227, /* 240 */ + 229, /* 241 */ + 230, /* 242 */ + 339, /* 243 */ + 373, /* 244 */ + 253, /* 245 */ + 245, /* 246 */ + 248, /* 247 */ + 254, /* 248 */ + 331, /* 249 */ + 341, /* 250 */ + 263, /* 251 */ + 347, /* 252 */ + 378, /* 253 */ + 63, /* 254 */ + 32, /* 255 */ +}; + +gchar * rds_decode(const gchar *s) +{ + guint l = strlen(s); + GString *b = g_string_sized_new(2 * l); + + guchar *c = (guchar*) s; + while (*c) { + g_string_append_unichar(b, rds_table[*c]); + c++; + } + + gchar *r = b->str; + g_string_free(b, FALSE); + return r; +} + @@ -0,0 +1,8 @@ +#ifndef _CFM_RDS_H_ +#define _CFM_RDS_H_ + +#include <glib.h> + +gchar * rds_decode(const gchar *s); + +#endif @@ -0,0 +1,289 @@ +/* + * GPL 2 + */ + +#include <math.h> +#include <string.h> +#include <gtk/gtk.h> +#include <cairo.h> + +#include "tuner.h" + +G_DEFINE_TYPE(CFmTuner, cfm_tuner, GTK_TYPE_DRAWING_AREA); + +#define CFM_TUNER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CFM_TYPE_TUNER, CFmTunerPrivate)) + +#define ABS_RANGE_LOW 60000000 +#define ABS_RANGE_HIGH 140000000 + +#define SCALE_TENTH_MHZ_PIXELS 15.0 +#define LABELS_FONT_SPEC "Nokia Sans 18" + +struct _CFmTunerPrivate { + gulong range_low, range_high; + gulong freq; + gboolean dragging; + gdouble drag_start_x; +}; + +enum { + PROP_0, + PROP_FREQUENCY, + PROP_RANGE_LOW, + PROP_RANGE_HIGH, + PROP_LAST +}; + +enum { + SIGNAL_0, + SIGNAL_FREQUENCY_CHANGED, + SIGNAL_FREQUENCY_TUNED, + SIGNAL_LAST +}; + +static GParamSpec *properties[PROP_LAST]; +static guint signals[SIGNAL_LAST]; + +static void cfm_tuner_draw(CFmTuner *self, cairo_t *cr) +{ + const GtkAllocation *size = &( GTK_WIDGET(self)->allocation ); + const double w = size->width, h = size->height; + const double midx = w / 2; + const double cur_f = self->priv->freq / 100000.0; // Tenths of a Mhz + const double left_f = cur_f - (midx / SCALE_TENTH_MHZ_PIXELS); + + double left_f_int; + double left_f_frac = modf(left_f, &left_f_int); + + static gchar text[12]; + PangoLayout *layout = pango_cairo_create_layout(cr); + PangoFontDescription *desc = pango_font_description_from_string(LABELS_FONT_SPEC); + double x = (1.0 - left_f_frac) * SCALE_TENTH_MHZ_PIXELS; + gulong f = left_f_int + 1; + cairo_set_source_rgb(cr, 1, 1, 1); + pango_layout_set_font_description(layout, desc); + pango_font_description_free(desc); + while (x <= w) { + if (f % 10 == 0) { + gint tw, th; + sprintf(text, "%lu", f / 10); + pango_layout_set_text(layout, text, -1); + pango_cairo_update_layout(cr, layout); + pango_layout_get_size(layout, &tw, &th); + cairo_move_to(cr, x - (tw / (PANGO_SCALE * 2)), 0.0); + pango_cairo_show_layout(cr, layout); + cairo_move_to(cr, x, 0.3 * h); + cairo_line_to(cr, x, 0.7 * h); + } else { + cairo_move_to(cr, x, 0.4 * h); + cairo_line_to(cr, x, 0.55 * h); + } + cairo_stroke(cr); + x += SCALE_TENTH_MHZ_PIXELS; + f += 1; + } + + + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_move_to(cr, w / 2, 0.25 * h); + cairo_line_to(cr, w / 2, h); + cairo_stroke(cr); +} + +static void cfm_tuner_redraw(CFmTuner *tuner) +{ + GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(tuner)); + GdkRegion *region = gdk_drawable_get_clip_region(GDK_DRAWABLE(window)); + gdk_window_invalidate_region(window, region, TRUE); +} + +static void cfm_tuner_realize(GtkWidget *widget) +{ + GTK_WIDGET_CLASS(cfm_tuner_parent_class)->realize(widget); + + gtk_widget_add_events(widget, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK); +} + + +static gboolean cfm_tuner_button_press(GtkWidget *widget, GdkEventButton *event) +{ + CFmTuner *self = CFM_TUNER(widget); + CFmTunerPrivate *priv = self->priv; + + priv->dragging = TRUE; + priv->drag_start_x = event->x; + + return FALSE; +} + +static gboolean cfm_tuner_button_release(GtkWidget *widget, GdkEventButton *event) +{ + CFmTuner *self = CFM_TUNER(widget); + CFmTunerPrivate *priv = self->priv; + + if (priv->dragging) { + priv->dragging = FALSE; + priv->freq = lrint(priv->freq / 100000.0) * 100000; /* Round it. */ + cfm_tuner_redraw(self); + g_signal_emit(G_OBJECT(self), signals[SIGNAL_FREQUENCY_TUNED], 0, NULL); + } + + return FALSE; +} + +static gboolean cfm_tuner_motion_notify(GtkWidget *widget, GdkEventMotion *event) +{ + CFmTuner *self = CFM_TUNER(widget); + CFmTunerPrivate *priv = self->priv; + + if (priv->dragging) { + gdouble moved = (priv->drag_start_x - event->x) / SCALE_TENTH_MHZ_PIXELS; + priv->freq += moved * 100000.0; + if (priv->freq < priv->range_low) { + priv->freq = priv->range_low; + } else if (priv->freq > priv->range_high) { + priv->freq = priv->range_high; + } + priv->drag_start_x = event->x; + cfm_tuner_redraw(self); + g_signal_emit(G_OBJECT(self), signals[SIGNAL_FREQUENCY_CHANGED], 0, NULL); + } + + return FALSE; +} + +static void cfm_tuner_size_request(GtkWidget *widget, GtkRequisition *requisition) +{ + requisition->width = 120; + requisition->height = 120; +} + +static gboolean cfm_tuner_expose(GtkWidget *widget, GdkEventExpose *event) +{ + CFmTuner *self = CFM_TUNER(widget); + cairo_t *cr = gdk_cairo_create(widget->window); + cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, + event->area.height); + cairo_clip(cr); + + cfm_tuner_draw(self, cr); + + cairo_destroy(cr); + return FALSE; +} + +static void cfm_tuner_set_property(GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + CFmTuner *self = CFM_TUNER(object); + switch (property_id) { + case PROP_FREQUENCY: + self->priv->freq = g_value_get_ulong(value); + cfm_tuner_redraw(self); + break; + case PROP_RANGE_LOW: + self->priv->range_low = g_value_get_ulong(value); + cfm_tuner_redraw(self); + break; + case PROP_RANGE_HIGH: + self->priv->range_high = g_value_get_ulong(value); + cfm_tuner_redraw(self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void cfm_tuner_get_property(GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + CFmTuner *self = CFM_TUNER(object); + switch (property_id) { + case PROP_FREQUENCY: + g_value_set_ulong(value, self->priv->freq); + break; + case PROP_RANGE_LOW: + g_value_set_ulong(value, self->priv->range_low); + break; + case PROP_RANGE_HIGH: + g_value_set_ulong(value, self->priv->range_high); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void cfm_tuner_init(CFmTuner *self) +{ + CFmTunerPrivate *priv; + + self->priv = priv = CFM_TUNER_GET_PRIVATE(self); +} + +static void cfm_tuner_dispose(GObject *object) +{ +} + +static void cfm_tuner_finalize(GObject *object) +{ +} + +static void cfm_tuner_class_init(CFmTunerClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + GParamSpec *param_spec; + + gobject_class->set_property = cfm_tuner_set_property; + gobject_class->get_property = cfm_tuner_get_property; + gobject_class->dispose = cfm_tuner_dispose; + gobject_class->finalize = cfm_tuner_finalize; + widget_class->realize = cfm_tuner_realize; + widget_class->button_press_event = cfm_tuner_button_press; + widget_class->button_release_event = cfm_tuner_button_release; + widget_class->motion_notify_event = cfm_tuner_motion_notify; + widget_class->size_request = cfm_tuner_size_request; + widget_class->expose_event = cfm_tuner_expose; + + g_type_class_add_private (klass, sizeof(CFmTunerPrivate)); + + param_spec = g_param_spec_ulong("frequency", + "Frequency to tune (Hz)", + "This is the frequency to tune, in Hz", + ABS_RANGE_LOW, ABS_RANGE_HIGH, ABS_RANGE_LOW, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_FREQUENCY] = param_spec; + g_object_class_install_property(gobject_class, PROP_FREQUENCY, param_spec); + + param_spec = g_param_spec_ulong("range-low", + "Min frequency range (Hz)", + "This is the lowest frequency that can be tuned, in Hz", + ABS_RANGE_LOW, ABS_RANGE_HIGH, ABS_RANGE_LOW, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_property(gobject_class, PROP_RANGE_LOW, param_spec); + properties[PROP_RANGE_LOW] = param_spec; + param_spec = g_param_spec_ulong("range-high", + "Max frequency range (Hz)", + "This is the highest frequency that can be tuned, in Hz", + ABS_RANGE_LOW, ABS_RANGE_HIGH, ABS_RANGE_HIGH, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_RANGE_HIGH] = param_spec; + g_object_class_install_property(gobject_class, PROP_RANGE_HIGH, param_spec); + + signals[SIGNAL_FREQUENCY_CHANGED] = g_signal_new("frequency-changed", + G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + signals[SIGNAL_FREQUENCY_TUNED] = g_signal_new("frequency-tuned", + G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + +CFmTuner* cfm_tuner_new() +{ + return g_object_new(CFM_TYPE_TUNER, NULL); +} + @@ -0,0 +1,36 @@ +/* + * GPL 2 + */ + +#ifndef __CFM_TUNER_H__ +#define __CFM_TUNER_H__ + +#include <gtk/gtk.h> + +#define CFM_TYPE_TUNER (cfm_tuner_get_type ()) +#define CFM_TUNER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CFM_TYPE_TUNER, CFmTuner)) +#define CFM_IS_TUNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CFM_TYPE_TUNER)) +#define CFM_TUNER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CFM_TYPE_TUNER, CFmTunerClass)) +#define CFM_IS_TUNER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CFM_TYPE_TUNER)) +#define CFM_TUNER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CFM_TYPE_TUNER, CFmTunerClass)) + +typedef struct _CFmTuner CFmTuner; +typedef struct _CFmTunerPrivate CFmTunerPrivate; +typedef struct _CFmTunerClass CFmTunerClass; + +struct _CFmTuner +{ + GtkDrawingArea parent; + CFmTunerPrivate *priv; +}; + +struct _CFmTunerClass +{ + GtkDrawingAreaClass parent; +}; + +GType cfm_tuner_get_type (void); +CFmTuner* cfm_tuner_new(); + +#endif /* __CFM_TUNER_H__ */ + @@ -0,0 +1,22 @@ + +#include <glib-object.h> +#include "types.h" + +GType +cfm_radio_output_get_type(void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + { CFM_RADIO_OUTPUT_MUTE, "CFM_RADIO_OUTPUT_MUTE", "mute" }, + { CFM_RADIO_OUTPUT_SYSTEM, "CFM_RADIO_OUTPUT_SYSTEM", "system" }, + { CFM_RADIO_OUTPUT_SPEAKER, "CFM_RADIO_OUTPUT_SPEAKER", "speaker" }, + { CFM_RADIO_OUTPUT_HEADPHONES, "CFM_RADIO_OUTPUT_HEADPHONES", "headphones" }, + { 0, NULL, NULL } + }; + etype = g_enum_register_static("CFmRadioOutput", values); + } + return etype; +} + @@ -0,0 +1,22 @@ + +#ifndef __CFMRADIO_TYPES_H__ +#define __CFMRADIO_TYPES_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef enum { + CFM_RADIO_OUTPUT_MUTE = 0, + CFM_RADIO_OUTPUT_SYSTEM, + CFM_RADIO_OUTPUT_SPEAKER, + CFM_RADIO_OUTPUT_HEADPHONES +} CFmRadioOutput; + +GType cfm_radio_output_get_type(void) G_GNUC_CONST; +#define CFM_TYPE_RADIO_OUTPUT (cfm_radio_output_get_type()) + +G_END_DECLS + +#endif /*__CFMRADIO_TYPES_H__ */ + |