├── src ├── icon.rc ├── antpatt.h ├── scheduler.h ├── ui-audio.h ├── version.h ├── rds-utils.h ├── rdsspy.h ├── tuner-filters.h ├── ui-connect.h ├── scan.h ├── ui-signal.h ├── log.h ├── win32.h ├── tuner-scan.h ├── settings.h ├── ui-input.h ├── stationlist.h ├── ui-tuner-set.h ├── ui-tuner-update.h ├── tuner-callbacks.h ├── tuner-conn.h ├── scheduler.c ├── CMakeLists.txt ├── tuner-scan.c ├── rds-utils.c ├── main.c ├── tuner.h ├── ui.h ├── win32.c ├── antpatt.c ├── tuner-filters.c ├── conf.h ├── log.c ├── conf-defaults.h ├── rdsspy.c ├── ui-audio.c ├── tuner-conn.c ├── ui-tuner-set.c ├── tuner-callbacks.c ├── ui-signal.c ├── stationlist.c ├── ui-input.c ├── ui-connect.c └── tuner.c ├── xdr-gtk.png ├── icons ├── icon.ico ├── 16x16 │ └── apps │ │ └── xdr-gtk.png ├── 22x22 │ └── apps │ │ └── xdr-gtk.png ├── 24x24 │ └── apps │ │ ├── xdr-gtk.png │ │ ├── xdr-gtk-top.png │ │ ├── xdr-gtk-rdsspy.png │ │ ├── xdr-gtk-scan.png │ │ ├── xdr-gtk-connect.png │ │ ├── xdr-gtk-pattern.png │ │ ├── xdr-gtk-settings.png │ │ ├── xdr-gtk-scheduler.png │ │ ├── xdr-gtk-squelch-off.png │ │ ├── xdr-gtk-squelch-on.png │ │ └── xdr-gtk-squelch-st.png ├── 32x32 │ └── apps │ │ └── xdr-gtk.png ├── 48x48 │ └── apps │ │ └── xdr-gtk.png ├── INFO ├── icons.xml └── scalable │ └── apps │ └── xdr-gtk.svg ├── .gitmodules ├── xdr-gtk.desktop ├── README.md ├── CMakeLists.txt └── LICENSE /src/icon.rc: -------------------------------------------------------------------------------- 1 | 1 ICON "../icons/icon.ico" 2 | -------------------------------------------------------------------------------- /xdr-gtk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/xdr-gtk.png -------------------------------------------------------------------------------- /icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/icon.ico -------------------------------------------------------------------------------- /icons/16x16/apps/xdr-gtk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/16x16/apps/xdr-gtk.png -------------------------------------------------------------------------------- /icons/22x22/apps/xdr-gtk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/22x22/apps/xdr-gtk.png -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk.png -------------------------------------------------------------------------------- /icons/32x32/apps/xdr-gtk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/32x32/apps/xdr-gtk.png -------------------------------------------------------------------------------- /icons/48x48/apps/xdr-gtk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/48x48/apps/xdr-gtk.png -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk-top.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "librdsparser"] 2 | path = librdsparser 3 | url = https://github.com/kkonradpl/librdsparser 4 | -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk-rdsspy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk-rdsspy.png -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk-scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk-scan.png -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk-connect.png -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk-pattern.png -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk-settings.png -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk-scheduler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk-scheduler.png -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk-squelch-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk-squelch-off.png -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk-squelch-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk-squelch-on.png -------------------------------------------------------------------------------- /icons/24x24/apps/xdr-gtk-squelch-st.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkonradpl/xdr-gtk/HEAD/icons/24x24/apps/xdr-gtk-squelch-st.png -------------------------------------------------------------------------------- /src/antpatt.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_ANTPATT_H_ 2 | #define XDR_ANTPATT_H_ 3 | 4 | void antpatt_toggle(void); 5 | void antpatt_push(gfloat); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/scheduler.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_SCHEDULER_H_ 2 | #define XDR_SCHEDULER_H_ 3 | 4 | void scheduler_toggle(); 5 | void scheduler_start(); 6 | void scheduler_stop(); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /src/ui-audio.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_GUI_AUDIO_H_ 2 | #define XDR_GUI_AUDIO_H_ 3 | #include "ui.h" 4 | 5 | GtkWidget* volume_init(gint); 6 | GtkWidget* squelch_init(gint); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_VERSION_H_ 2 | #define XDR_VERSION_H_ 3 | 4 | #define APP_NAME "XDR-GTK" 5 | #define APP_VERSION "1.2" 6 | #define APP_ICON "xdr-gtk" 7 | 8 | #endif 9 | 10 | -------------------------------------------------------------------------------- /src/rds-utils.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_RDS_UTILS_H_ 2 | #define XDR_RDS_UTILS_H_ 3 | #include 4 | 5 | gchar* rds_utils_text(const rdsparser_string_t*); 6 | gchar* rds_utils_text_markup(const rdsparser_string_t*, gboolean); 7 | 8 | #endif 9 | 10 | -------------------------------------------------------------------------------- /xdr-gtk.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Name=XDR-GTK 5 | Comment=User interface for XDR-F1HD tuner 6 | Icon=xdr-gtk 7 | Exec=xdr-gtk 8 | Terminal=false 9 | Categories=Network; 10 | StartupWMClass=xdr-gtk 11 | StartupNotify=false 12 | -------------------------------------------------------------------------------- /src/rdsspy.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_RDSSPY_H_ 2 | #define XDR_RDSSPY_H_ 3 | 4 | void rdsspy_toggle(); 5 | gboolean rdsspy_is_up(); 6 | gboolean rdsspy_is_connected(); 7 | void rdsspy_stop(); 8 | 9 | void rdsspy_reset(); 10 | void rdsspy_send(guint*, guint); 11 | 12 | #endif 13 | 14 | -------------------------------------------------------------------------------- /src/tuner-filters.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_TUNER_FILTERS_H_ 2 | #define XDR_TUNER_FILTERS_H_ 3 | 4 | gint tuner_filter_from_index(gint); 5 | gint tuner_filter_bw(gint); 6 | 7 | gint tuner_filter_bw_from_index(gint); 8 | gint tuner_filter_index_from_bw(gint); 9 | gint tuner_filter_count(); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/ui-connect.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_UI_CONNECT_H 2 | #define XDR_UI_CONNECT_H 3 | #include 4 | 5 | void connection_toggle(); 6 | void connection_dialog(gboolean); 7 | gboolean connection_socket_callback(gpointer); 8 | gboolean connection_socket_callback_info(gpointer); 9 | void connection_socket_auth_fail(); 10 | 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /src/scan.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_SCAN_H_ 2 | #define XDR_SCAN_H_ 3 | #include "tuner-scan.h" 4 | 5 | void scan_init(); 6 | void scan_dialog(); 7 | 8 | void scan_update(tuner_scan_t*); 9 | void scan_update_value(gint, gfloat); 10 | 11 | void scan_try_toggle(gboolean); 12 | void scan_try_prev(); 13 | void scan_try_next(); 14 | void scan_force_redraw(); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/ui-signal.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_UI_SIGNAL_H_ 2 | #define XDR_UI_SIGNAL_H_ 3 | 4 | void signal_init(); 5 | void signal_resize(); 6 | 7 | void signal_push(gfloat, gboolean, gboolean, gboolean); 8 | void signal_separator(); 9 | void signal_clear(); 10 | 11 | gfloat signal_level(gfloat); 12 | const gchar* signal_unit(); 13 | void signal_display(); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /icons/INFO: -------------------------------------------------------------------------------- 1 | xdr-gtk - main icon by Marek Farkaš 2 | xdr-gtk-pattern - antpatt icon 3 | xdr-gtk-schedule - GNOME-Colors icon: clock 4 | xdr-gtk-rdsspy - icon from RDS Spy by Jan Kolar 5 | xdr-gtk-scan - GTK stock icon: edit-find 6 | xdr-gtk-settings - GTK stock icon: gtk-preferences 7 | xdr-gtk-squelch - modified versions of GNOME multimedia icon 8 | xdr-gtk-top - based on GNOME view-fullscreen icon 9 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_LOG_H_ 2 | #define XDR_LOG_H_ 3 | 4 | void log_cleanup(); 5 | void log_pi(gint, gint); 6 | void log_af(const gchar*); 7 | void log_ps(const gchar*, gboolean); 8 | void log_rt(guint8, const gchar*); 9 | void log_pty(const gchar*); 10 | void log_ecc(const gchar* ecc, guint); 11 | void log_ct(const gchar*); 12 | gchar* replace_spaces(const gchar*); 13 | 14 | #endif 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/win32.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_WIN32_H_ 2 | #define XDR_WIN32_H_ 3 | #include 4 | 5 | void win32_init(void); 6 | void win32_cleanup(void); 7 | gboolean win32_uri(GtkWidget*, gchar*, gpointer); 8 | gint win32_dialog_workaround(GtkDialog*); 9 | void win32_grab_focus(GtkWindow*); 10 | void win32_dark_titlebar(GtkWidget*); 11 | void win32_realize(GtkWidget*, gpointer); 12 | gchar* strsep(gchar**, gchar*); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/tuner-scan.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_TUNER_SCAN_H_ 2 | #define XDR_TUNER_SCAN_H_ 3 | 4 | typedef struct tuner_scan_node 5 | { 6 | gint freq; 7 | gfloat signal; 8 | } tuner_scan_node_t; 9 | 10 | typedef struct tuner_scan 11 | { 12 | tuner_scan_node_t* signals; 13 | gint len; 14 | gint min; 15 | gint max; 16 | } tuner_scan_t; 17 | 18 | tuner_scan_t* tuner_scan_parse(gchar*); 19 | tuner_scan_t* tuner_scan_copy(tuner_scan_t*); 20 | void tuner_scan_free(tuner_scan_t*); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/settings.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_SETTINGS_H_ 2 | #define XDR_SETTINGS_H_ 3 | #include 4 | 5 | #define SETTINGS_TAB_DEFAULT 0 6 | #define SETTINGS_TAB_INTERFACE 0 7 | #define SETTINGS_TAB_SIGNAL 1 8 | #define SETTINGS_TAB_RDS 2 9 | #define SETTINGS_TAB_ANTENNA 3 10 | #define SETTINGS_TAB_LOGS 4 11 | #define SETTINGS_TAB_KEYBOARD 5 12 | #define SETTINGS_TAB_PRESETS 6 13 | #define SETTINGS_TAB_SCHEDULER 7 14 | #define SETTINGS_TAB_ABOUT 8 15 | 16 | void settings_dialog(gint); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/ui-input.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_UI_INPUT_H_ 2 | #define XDR_UI_INPUT_H_ 3 | 4 | gboolean keyboard_press(GtkWidget*, GdkEventKey*, gpointer); 5 | gboolean keyboard_release(GtkWidget*, GdkEventKey*, gpointer); 6 | gboolean mouse_scroll(GtkWidget*, GdkEventScroll*, gpointer); 7 | gboolean mouse_window(GtkWidget*, GdkEventButton*, GtkWindow*); 8 | gboolean mouse_freq(GtkWidget*, GdkEvent*, gpointer); 9 | gboolean mouse_pi(GtkWidget*, GdkEvent*, gpointer); 10 | gboolean mouse_ps(GtkWidget*, GdkEventButton*, gpointer); 11 | gboolean mouse_rt(GtkWidget*, GdkEventButton*, gpointer); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/stationlist.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_STATIONLIST_H_ 2 | #define XDR_STATIONLIST_H_ 3 | 4 | typedef struct 5 | { 6 | gchar* param; 7 | gchar* value; 8 | } sl_data_t; 9 | 10 | void stationlist_init(); 11 | gboolean stationlist_is_up(); 12 | void stationlist_stop(); 13 | 14 | void stationlist_freq(gint); 15 | void stationlist_rcvlevel(gint); 16 | void stationlist_pi(gint); 17 | void stationlist_pty(gint); 18 | void stationlist_ecc(guchar); 19 | void stationlist_ps(const gchar*); 20 | void stationlist_rt(gint, const gchar*); 21 | void stationlist_bw(gint); 22 | void stationlist_af(gint); 23 | void stationlist_af_clear(); 24 | 25 | #endif 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/ui-tuner-set.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_UI_TUNER_SET_H_ 2 | #define XDR_UI_TUNER_SET_H_ 3 | #include 4 | 5 | #define TUNER_FREQ_MODIFY_DOWN 0 6 | #define TUNER_FREQ_MODIFY_UP 1 7 | #define TUNER_FREQ_MODIFY_RESET 2 8 | 9 | void tuner_set_frequency(gint); 10 | void tuner_set_frequency_prev(); 11 | void tuner_set_mode(gint); 12 | void tuner_set_bandwidth(); 13 | void tuner_set_deemphasis(); 14 | void tuner_set_volume(); 15 | void tuner_set_squelch(); 16 | void tuner_set_antenna(); 17 | void tuner_set_agc(); 18 | void tuner_set_gain(); 19 | void tuner_set_alignment(); 20 | void tuner_set_rotator(gpointer); 21 | void tuner_set_forced_mono(gboolean); 22 | void tuner_set_stereo_test(); 23 | void tuner_set_sampling_interval(gint, gboolean); 24 | void tuner_modify_frequency(guint); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/ui-tuner-update.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_UI_TUNER_UPDATE_H_ 2 | #define XDR_UI_TUNER_UPDATE_H_ 3 | #include "tuner.h" 4 | #include "tuner-scan.h" 5 | 6 | void ui_update_freq(); 7 | void ui_update_mode(); 8 | void ui_update_stereo_flag(); 9 | void ui_update_rds_flag(); 10 | void ui_update_signal(); 11 | void ui_update_cci(); 12 | void ui_update_cci_peak(); 13 | void ui_update_aci(); 14 | void ui_update_aci_peak(); 15 | 16 | void ui_update_rds_init(); 17 | 18 | void ui_update_pi(); 19 | void ui_update_tp(); 20 | void ui_update_ta(); 21 | void ui_update_ms(); 22 | void ui_update_pty(); 23 | void ui_update_country(); 24 | void ui_update_ps(); 25 | void ui_update_rt(gboolean); 26 | void ui_update_af(gint); 27 | 28 | void ui_update_bandwidth(); 29 | void ui_update_rotator(); 30 | void ui_update_forced_mono(); 31 | void ui_update_scan(tuner_scan_t*); 32 | void ui_update_disconnected(); 33 | void ui_update_pilot(gint); 34 | void ui_action(); 35 | void ui_unauthorized(); 36 | void ui_unauthorized(); 37 | void ui_clear_af(); 38 | 39 | void ui_update_service(); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /icons/icons.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icons/16x16/apps/xdr-gtk.png 5 | icons/22x22/apps/xdr-gtk.png 6 | icons/24x24/apps/xdr-gtk-squelch-st.png 7 | icons/24x24/apps/xdr-gtk-scheduler.png 8 | icons/24x24/apps/xdr-gtk-pattern.png 9 | icons/24x24/apps/xdr-gtk-squelch-off.png 10 | icons/24x24/apps/xdr-gtk-squelch-on.png 11 | icons/24x24/apps/xdr-gtk-scan.png 12 | icons/24x24/apps/xdr-gtk-settings.png 13 | icons/24x24/apps/xdr-gtk.png 14 | icons/24x24/apps/xdr-gtk-rdsspy.png 15 | icons/24x24/apps/xdr-gtk-top.png 16 | icons/24x24/apps/xdr-gtk-connect.png 17 | icons/32x32/apps/xdr-gtk.png 18 | icons/48x48/apps/xdr-gtk.png 19 | icons/scalable/apps/xdr-gtk.svg 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/tuner-callbacks.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_TUNER_CALLBACKS_H_ 2 | #define XDR_TUNER_CALLBACKS_H_ 3 | #include 4 | 5 | gboolean tuner_ready(gpointer); 6 | gboolean tuner_unauthorized(gpointer); 7 | gboolean tuner_disconnect(gpointer); 8 | gboolean tuner_freq(gpointer); 9 | gboolean tuner_daa(gpointer); 10 | gboolean tuner_signal(gpointer); 11 | gboolean tuner_cci(gpointer); 12 | gboolean tuner_aci(gpointer); 13 | gboolean tuner_pi(gpointer); 14 | gboolean tuner_rds_legacy(gpointer); 15 | gboolean tuner_rds_new(gpointer); 16 | gboolean tuner_scan(gpointer); 17 | gboolean tuner_pilot(gpointer); 18 | gboolean tuner_volume(gpointer); 19 | gboolean tuner_agc(gpointer); 20 | gboolean tuner_deemphasis(gpointer); 21 | gboolean tuner_antenna(gpointer); 22 | gboolean tuner_event(gpointer); 23 | gboolean tuner_gain(gpointer); 24 | gboolean tuner_mode(gpointer); 25 | gboolean tuner_filter(gpointer); 26 | gboolean tuner_bandwidth(gpointer); 27 | gboolean tuner_squelch(gpointer); 28 | gboolean tuner_rotator(gpointer); 29 | gboolean tuner_sampling_interval(gpointer); 30 | gboolean tuner_online(gpointer); 31 | gboolean tuner_online_guests(gpointer); 32 | 33 | #endif 34 | 35 | -------------------------------------------------------------------------------- /src/tuner-conn.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_TUNER_CONN_H 2 | #define XDR_TUNER_CONN_H 3 | #include 4 | #ifndef G_OS_WIN32 5 | #define closesocket(x) close(x) 6 | #endif 7 | 8 | #define MODE_SERIAL 0 9 | #define MODE_SOCKET 1 10 | 11 | #define CONN_SUCCESS 0 12 | 13 | #define CONN_SERIAL_FAIL_OPEN -1 14 | #define CONN_SERIAL_FAIL_PARM_R -2 15 | #define CONN_SERIAL_FAIL_PARM_W -3 16 | #define CONN_SERIAL_FAIL_SPEED -4 17 | 18 | #define CONN_SOCKET_STATE_UNDEF 100 19 | #define CONN_SOCKET_STATE_RESOLV 1 20 | #define CONN_SOCKET_STATE_CONN 2 21 | #define CONN_SOCKET_STATE_AUTH 3 22 | #define CONN_SOCKET_FAIL_RESOLV -1 23 | #define CONN_SOCKET_FAIL_CONN -2 24 | #define CONN_SOCKET_FAIL_AUTH -3 25 | #define CONN_SOCKET_FAIL_WRITE -4 26 | 27 | #define SOCKET_SALT_LEN 16 28 | #define SOCKET_AUTH_TIMEOUT 5 29 | 30 | typedef struct conn 31 | { 32 | gchar* hostname; 33 | gchar* port; 34 | gchar* password; 35 | volatile gboolean canceled; 36 | gint socketfd; 37 | gint state; 38 | } conn_t; 39 | 40 | gint tuner_open_serial(const gchar*, gintptr*); 41 | gpointer tuner_open_socket(gpointer); 42 | 43 | conn_t* conn_new(const gchar*, const gchar* port, const gchar*); 44 | void conn_free(conn_t*); 45 | #endif 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | XDR-GTK 2 | ======= 3 | 4 | User interface for XDR-F1HD tuner with XDR-I2C modification. 5 | 6 | ![Screenshot](/xdr-gtk.png?raw=true) 7 | 8 | Copyright (C) 2012-2024 Konrad Kosmatka 9 | 10 | https://fmdx.pl/xdr-gtk/ 11 | 12 | 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. 13 | 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. 14 | 15 | # Build 16 | In order to build XDR-GTK you will need: 17 | - CMake 18 | - C compiler 19 | - GTK+ 3 & dependencies 20 | - librdsparser (https://github.com/kkonradpl/librdsparser) 21 | 22 | The librdsparser is available as submodule. 23 | 24 | Clone the repository with: 25 | ```sh 26 | $ git clone --recurse-submodules https://github.com/kkonradpl/xdr-gtk 27 | ``` 28 | 29 | Once you have all the necessary dependencies, you can use scripts available in the `build` directory. 30 | 31 | # Installation 32 | After a successful build, just use: 33 | ```sh 34 | $ sudo make install 35 | ``` 36 | in the `build` directory. 37 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | project(xdr-gtk) 3 | 4 | find_package(PkgConfig REQUIRED) 5 | 6 | pkg_check_modules(GTK REQUIRED gtk+-3.0) 7 | include_directories(${GTK_INCLUDE_DIRS}) 8 | include_directories(librdsparser/include) 9 | link_directories(${GTK_LIBRARY_DIRS}) 10 | add_definitions(${GTK_CFLAGS_OTHER}) 11 | 12 | find_program(GLIB_COMPILE_RESOURCES NAMES glib-compile-resources REQUIRED) 13 | execute_process(COMMAND ${GLIB_COMPILE_RESOURCES} --generate-source --sourcedir=${CMAKE_SOURCE_DIR} --target=${CMAKE_BINARY_DIR}/resources.c ${CMAKE_SOURCE_DIR}/icons/icons.xml) 14 | execute_process(COMMAND ${GLIB_COMPILE_RESOURCES} --generate-header --sourcedir=${CMAKE_SOURCE_DIR} --target=${CMAKE_BINARY_DIR}/resources.h ${CMAKE_SOURCE_DIR}/icons/icons.xml) 15 | 16 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic -Wno-overlength-strings") 17 | 18 | option(RDSPARSER_DISABLE_TESTS "" ON) 19 | option(RDSPARSER_DISABLE_EXAMPLES "" ON) 20 | add_subdirectory(librdsparser) 21 | 22 | add_subdirectory(src) 23 | 24 | if(NOT MINGW) 25 | install(TARGETS xdr-gtk DESTINATION bin) 26 | install(FILES xdr-gtk.desktop DESTINATION share/applications) 27 | install(DIRECTORY icons/ DESTINATION share/icons/hicolor 28 | FILES_MATCHING 29 | PATTERN "xdr-gtk\.png" 30 | PATTERN "xdr-gtk\.svg") 31 | install(CODE "execute_process(COMMAND gtk-update-icon-cache /usr/share/icons/hicolor)") 32 | endif() 33 | -------------------------------------------------------------------------------- /src/scheduler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "scheduler.h" 3 | #include "ui.h" 4 | #include "ui-tuner-set.h" 5 | #include "conf.h" 6 | 7 | static guint scheduler_id = 0; 8 | static guint scheduler_next; 9 | 10 | static gboolean scheduler_switch(gpointer); 11 | static void scheduler_toggle_button(gboolean); 12 | 13 | void 14 | scheduler_toggle() 15 | { 16 | if(!scheduler_id) 17 | scheduler_start(); 18 | else 19 | scheduler_stop(); 20 | } 21 | 22 | void 23 | scheduler_start() 24 | { 25 | if(!conf.scheduler_n) 26 | { 27 | scheduler_toggle_button(FALSE); 28 | ui_dialog(ui.window, 29 | GTK_MESSAGE_INFO, 30 | "Frequency scheduler", 31 | "No scheduler frequencies defined.\nAdd some in settings first."); 32 | return; 33 | } 34 | 35 | scheduler_next = 0; 36 | scheduler_switch(NULL); 37 | scheduler_toggle_button(TRUE); 38 | } 39 | 40 | void 41 | scheduler_stop() 42 | { 43 | if(!scheduler_id) 44 | return; 45 | 46 | scheduler_toggle_button(FALSE); 47 | g_source_remove(scheduler_id); 48 | scheduler_id = 0; 49 | } 50 | 51 | static gboolean 52 | scheduler_switch(gpointer data) 53 | { 54 | tuner_set_frequency(conf.scheduler_freqs[scheduler_next]); 55 | scheduler_id = g_timeout_add(conf.scheduler_timeouts[scheduler_next]*1000, scheduler_switch, NULL); 56 | scheduler_next = (scheduler_next + 1)%conf.scheduler_n; 57 | return FALSE; 58 | } 59 | 60 | static void 61 | scheduler_toggle_button(gboolean active) 62 | { 63 | g_signal_handlers_block_by_func(G_OBJECT(ui.b_scheduler), GINT_TO_POINTER(scheduler_toggle), NULL); 64 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_scheduler), GPOINTER_TO_INT(active)); 65 | g_signal_handlers_unblock_by_func(G_OBJECT(ui.b_scheduler), GINT_TO_POINTER(scheduler_toggle), NULL); 66 | } 67 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | 3 | set(SOURCE_FILES 4 | antpatt.c 5 | antpatt.h 6 | conf.c 7 | conf.h 8 | conf-defaults.h 9 | log.c 10 | log.h 11 | main.c 12 | rds-utils.c 13 | rds-utils.h 14 | rdsspy.c 15 | rdsspy.h 16 | scan.c 17 | scan.h 18 | scheduler.c 19 | scheduler.h 20 | settings.c 21 | settings.h 22 | stationlist.c 23 | stationlist.h 24 | tuner.c 25 | tuner.h 26 | tuner-callbacks.c 27 | tuner-callbacks.h 28 | tuner-conn.c 29 | tuner-conn.h 30 | tuner-filters.c 31 | tuner-filters.h 32 | tuner-scan.c 33 | tuner-scan.h 34 | ui.c 35 | ui.h 36 | ui-audio.c 37 | ui-audio.h 38 | ui-connect.c 39 | ui-connect.h 40 | ui-input.c 41 | ui-input.h 42 | ui-signal.c 43 | ui-signal.h 44 | ui-tuner-set.c 45 | ui-tuner-set.h 46 | ui-tuner-update.c 47 | ui-tuner-update.h 48 | version.h 49 | ${CMAKE_BINARY_DIR}/resources.c) 50 | 51 | set(SOURCE_FILES_MINGW 52 | win32.c 53 | win32.h 54 | icon.rc) 55 | 56 | set(LIBRARIES 57 | rdsparser_static 58 | ${GTK_LIBRARIES} 59 | m) 60 | 61 | set(LIBRARIES_MINGW 62 | ws2_32) 63 | 64 | if(MINGW) 65 | IF(NOT (CMAKE_BUILD_TYPE MATCHES Debug)) 66 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mwindows") 67 | ENDIF() 68 | set(CMAKE_RC_COMPILER_INIT windres) 69 | ENABLE_LANGUAGE(RC) 70 | SET(CMAKE_RC_COMPILE_OBJECT " -i -o ") 71 | add_executable(xdr-gtk ${SOURCE_FILES} ${SOURCE_FILES_MINGW}) 72 | target_link_libraries(xdr-gtk ${LIBRARIES} ${LIBRARIES_MINGW}) 73 | ELSE() 74 | add_executable(xdr-gtk ${SOURCE_FILES}) 75 | target_link_libraries(xdr-gtk ${LIBRARIES}) 76 | ENDIF() 77 | 78 | target_include_directories(xdr-gtk PRIVATE ${CMAKE_BINARY_DIR}) 79 | -------------------------------------------------------------------------------- /src/tuner-scan.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "tuner-scan.h" 6 | #ifdef G_OS_WIN32 7 | #include "win32.h" 8 | #endif 9 | 10 | tuner_scan_t* 11 | tuner_scan_parse(gchar *msg) 12 | { 13 | tuner_scan_t *scan; 14 | gchar *ptr, *freq, *value; 15 | gint n = 0; 16 | gint i; 17 | 18 | if(!msg) 19 | return NULL; 20 | 21 | for(i=0; imin = G_MAXINT; 30 | scan->max = G_MININT; 31 | scan->signals = g_new(tuner_scan_node_t, n); 32 | 33 | i = 0; 34 | while((ptr = strsep(&msg, ",")) && i < n) 35 | { 36 | freq = strsep(&ptr, "="); 37 | value = strsep(&ptr, "="); 38 | if(freq && value) 39 | { 40 | scan->signals[i].freq = atoi(freq); 41 | scan->signals[i].signal = g_ascii_strtod(value, NULL); 42 | if(scan->signals[i].signal > scan->max) 43 | scan->max = ceil(scan->signals[i].signal); 44 | if(scan->signals[i].signal < scan->min) 45 | scan->min = floor(scan->signals[i].signal); 46 | i++; 47 | } 48 | } 49 | 50 | if(!i) 51 | { 52 | tuner_scan_free(scan); 53 | return NULL; 54 | } 55 | 56 | scan->len = i; 57 | return scan; 58 | } 59 | 60 | tuner_scan_t* 61 | tuner_scan_copy(tuner_scan_t *data) 62 | { 63 | tuner_scan_t *copy; 64 | gint i; 65 | 66 | copy = g_new(tuner_scan_t, 1); 67 | copy->len = data->len; 68 | copy->max = data->max; 69 | copy->min = data->min; 70 | copy->signals = g_new(tuner_scan_node_t, data->len); 71 | for(i=0;ilen;i++) 72 | { 73 | copy->signals[i].freq = data->signals[i].freq; 74 | copy->signals[i].signal = data->signals[i].signal; 75 | } 76 | return copy; 77 | } 78 | 79 | void 80 | tuner_scan_free(tuner_scan_t *data) 81 | { 82 | g_free(data->signals); 83 | g_free(data); 84 | } 85 | -------------------------------------------------------------------------------- /src/rds-utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "ui.h" 4 | 5 | gchar* 6 | rds_utils_text(const rdsparser_string_t *rds_string) 7 | { 8 | GString *string = g_string_new(NULL); 9 | const wchar_t *content = rdsparser_string_get_content(rds_string); 10 | uint8_t len = rdsparser_string_get_length(rds_string); 11 | 12 | for (gint i = 0; i < len; i++) 13 | { 14 | gchar buffer[10]; 15 | gunichar unichar = content[i]; 16 | buffer[g_unichar_to_utf8(unichar, buffer)] = '\0'; 17 | 18 | g_string_append(string, buffer); 19 | } 20 | 21 | return g_string_free(string, FALSE); 22 | } 23 | 24 | gchar* 25 | rds_utils_text_markup(const rdsparser_string_t *rds_string, 26 | gboolean progressive) 27 | { 28 | GString *string = g_string_new(NULL); 29 | const wchar_t *content = rdsparser_string_get_content(rds_string); 30 | const uint8_t *errors = rdsparser_string_get_errors(rds_string); 31 | uint8_t len = rdsparser_string_get_length(rds_string); 32 | 33 | g_string_append_printf(string, 34 | "%c", 35 | progressive ? '(' : '['); 36 | 37 | for (gint i = 0; i < len; i++) 38 | { 39 | gchar buffer[10]; 40 | gunichar unichar = content[i]; 41 | buffer[g_unichar_to_utf8(unichar, buffer)] = '\0'; 42 | 43 | gchar* buffer_escaped = g_markup_printf_escaped("%s", buffer); 44 | const gint max_alpha = 70; 45 | const gint alpha_range = 50; 46 | gint alpha = errors[i] * (alpha_range / (RDSPARSER_STRING_ERROR_LARGEST + 1)); 47 | 48 | if (alpha) 49 | { 50 | g_string_append_printf(string, "%s", max_alpha - alpha, buffer_escaped); 51 | } 52 | else 53 | { 54 | g_string_append(string, buffer_escaped); 55 | } 56 | 57 | g_free(buffer_escaped); 58 | } 59 | 60 | g_string_append_printf(string, 61 | "%c", 62 | progressive ? ')' : ']'); 63 | 64 | return g_string_free(string, FALSE); 65 | } 66 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * XDR-GTK - user interface for XDR-F1HD tuner with XDR-I2C modification 3 | * Copyright (c) 2012-2023 Konrad Kosmatka 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | */ 15 | 16 | #include 17 | #include 18 | #include "ui.h" 19 | #include "conf.h" 20 | #include "rdsspy.h" 21 | #include "stationlist.h" 22 | #include "log.h" 23 | #include "resources.h" 24 | #ifdef G_OS_WIN32 25 | #include "win32.h" 26 | #endif 27 | 28 | static const gchar* 29 | get_config_path(gint argc, 30 | gchar *argv[]) 31 | { 32 | gchar *path = NULL; 33 | gint c; 34 | while((c = getopt(argc, argv, "c:")) != -1) 35 | { 36 | switch(c) 37 | { 38 | case 'c': 39 | path = optarg; 40 | break; 41 | case '?': 42 | if(optopt == 'c') 43 | fprintf(stderr, "No configuration path argument found, using default.\n"); 44 | break; 45 | } 46 | } 47 | return path; 48 | } 49 | 50 | gint 51 | main(gint argc, 52 | gchar *argv[]) 53 | { 54 | gtk_disable_setlocale(); 55 | gtk_init(&argc, &argv); 56 | 57 | g_resources_register(icons_get_resource()); 58 | gtk_icon_theme_add_resource_path(gtk_icon_theme_get_default(), "/org/xdr-gtk/icons"); 59 | 60 | #ifdef G_OS_WIN32 61 | win32_init(); 62 | #endif 63 | conf_init(get_config_path(argc, argv)); 64 | 65 | g_object_set(gtk_settings_get_default(), 66 | "gtk-application-prefer-dark-theme", 67 | conf.dark_theme, NULL); 68 | 69 | ui_init(); 70 | 71 | if (conf.rdsspy_auto) 72 | rdsspy_toggle(); 73 | 74 | if (conf.srcp) 75 | stationlist_init(); 76 | 77 | gtk_main(); 78 | log_cleanup(); 79 | #ifdef G_OS_WIN32 80 | win32_cleanup(); 81 | #endif 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /src/tuner.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_TUNER_H_ 2 | #define XDR_TUNER_H_ 3 | #include 4 | #include 5 | #include "conf.h" 6 | #include "tuner-filters.h" 7 | 8 | #define TUNER_THREAD_SERIAL 0 9 | #define TUNER_THREAD_SOCKET 1 10 | 11 | #define MODE_FM 0 12 | #define MODE_AM 1 13 | 14 | #define SIGNAL_MONO 0 15 | #define SIGNAL_STEREO 1 16 | #define SIGNAL_FORCED_MONO (1<<1) 17 | 18 | #define TUNER_FREQ_MIN 100 19 | #define TUNER_FREQ_MAX 200000 20 | 21 | typedef struct signal_data 22 | { 23 | gfloat level; 24 | gboolean rds; 25 | gboolean stereo; 26 | } signal_data_t; 27 | 28 | typedef struct tuner_signal 29 | { 30 | gfloat value; 31 | gboolean stereo; 32 | } tuner_signal_t; 33 | 34 | typedef struct tuner 35 | { 36 | gint mode; 37 | gint freq; 38 | gint prevfreq; 39 | gint prevantenna; 40 | 41 | gint sampling_interval; 42 | gfloat signal; 43 | gfloat signal_max; 44 | gdouble signal_sum; 45 | guint signal_samples; 46 | gboolean stereo; 47 | 48 | gint cci; 49 | gint aci; 50 | 51 | rdsparser_t *rds; 52 | gint rds_timeout; 53 | gint64 rds_reset_timer; 54 | gint rds_pi; 55 | gint rds_pi_err_level; 56 | 57 | gint daa; 58 | gint volume; 59 | gint agc; 60 | gint deemphasis; 61 | gint antenna; 62 | gint rfgain; 63 | gint ifgain; 64 | gint bandwidth; 65 | gint squelch; 66 | gint rotator; 67 | gboolean rotator_waiting; 68 | gboolean forced_mono; 69 | 70 | gboolean ready; 71 | gboolean ready_tuned; 72 | gboolean guest; 73 | gint online; 74 | gint online_guests; 75 | gboolean send_settings; 76 | gpointer thread; 77 | 78 | gint64 last_set_bandwidth; 79 | gint64 last_set_deemph; 80 | gint64 last_set_agc; 81 | gint64 last_set_ant; 82 | gint64 last_set_volume; 83 | gint64 last_set_squelch; 84 | gint64 last_set_gain; 85 | gint64 last_set_daa; 86 | gint64 last_set_rotator; 87 | gint64 last_set_pilot; 88 | 89 | gint offset[ANT_COUNT]; 90 | } tuner_t; 91 | 92 | extern tuner_t tuner; 93 | 94 | gpointer tuner_thread_new(gint, gintptr); 95 | gboolean tuner_write_socket(gintptr, gchar*, int); 96 | void tuner_thread_cancel(gpointer); 97 | void tuner_write(gpointer, gchar*); 98 | 99 | void tuner_rds_init(); 100 | void tuner_rds_configure(); 101 | void tuner_clear_all(); 102 | void tuner_clear_signal(); 103 | void tuner_clear_rds(); 104 | gint tuner_get_freq(); 105 | gint tuner_get_offset(); 106 | void tuner_set_offset(gint, gint); 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/ui.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_UI_H_ 2 | #define XDR_UI_H_ 3 | #include 4 | 5 | #ifdef G_OS_WIN32 6 | #define PATH_SEP "\\" 7 | #define LOG_NL "\n" 8 | #else 9 | #define PATH_SEP "/" 10 | #define LOG_NL "\n" 11 | #endif 12 | 13 | #define UI_ALPHA_INSENSITIVE "25" 14 | #define UI_ALPHA_SECONDARY "50" 15 | 16 | #define AF_LIST_STORE_ID 0 17 | #define AF_LIST_STORE_FREQ 1 18 | 19 | #define UI_COLOR_STEREO "#EE4000" 20 | 21 | typedef struct ui 22 | { 23 | GtkWidget *window; 24 | gchar window_title[100]; 25 | GdkCursor *click_cursor; 26 | GtkListStore *af_model; 27 | 28 | GtkWidget *frame; 29 | GtkWidget *margin; 30 | GtkWidget *box; 31 | GtkWidget *box_header; 32 | GtkWidget *box_ui; 33 | GtkWidget *box_left; 34 | GtkWidget *box_left_tune; 35 | GtkWidget *box_left_interference; 36 | GtkWidget *box_left_signal; 37 | GtkWidget *box_left_indicators; 38 | GtkWidget *box_left_settings1; 39 | GtkWidget *box_buttons; 40 | GtkWidget *box_left_settings2; 41 | GtkWidget *box_rotator; 42 | 43 | GtkWidget *volume; 44 | GtkWidget *squelch; 45 | GtkWidget *event_band, *l_band; 46 | GtkWidget *event_freq, *l_freq; 47 | GtkWidget *event_pi, *l_pi; 48 | GtkWidget *event_ps, *l_ps; 49 | 50 | GtkWidget *b_scan; 51 | GtkWidget *b_tune_back, *b_tune_back_label; 52 | GtkWidget *e_freq; 53 | GtkWidget *b_tune_reset; 54 | 55 | GtkAdjustment *adj_align; 56 | GtkWidget *hs_align; 57 | 58 | GtkWidget *p_cci, *p_aci; 59 | GtkWidget *l_cci, *l_aci; 60 | 61 | GtkWidget *graph, *p_signal; 62 | 63 | GtkWidget *event_st, *l_st; 64 | GtkWidget *l_rds; 65 | GtkWidget *l_tp; 66 | GtkWidget *l_ta; 67 | GtkWidget *l_ms; 68 | GtkWidget *l_pty; 69 | GtkWidget *l_country; 70 | GtkWidget *event_sig, *l_sig; 71 | 72 | GtkWidget *b_connect; 73 | GtkWidget *b_pattern; 74 | GtkWidget *b_settings; 75 | GtkWidget *b_scheduler; 76 | GtkWidget *b_rdsspy; 77 | GtkWidget *b_ontop; 78 | 79 | GtkWidget *c_ant; 80 | GtkListStore *ant; 81 | 82 | GtkWidget *b_cw, *b_cw_label; 83 | GtkWidget *b_ccw, *b_ccw_label; 84 | GtkWidget *l_agc, *c_agc; 85 | GtkWidget *x_rf; 86 | 87 | GtkWidget *l_deemph, *c_deemph; 88 | GtkWidget *l_bw, *c_bw; 89 | GtkWidget *x_if; 90 | 91 | GtkWidget *event_rt[2], *l_rt[2]; 92 | 93 | guint status_timeout; 94 | guint title_timeout; 95 | GtkWidget *l_status; 96 | 97 | GtkWidget *box_af; 98 | GtkWidget *l_af_title; 99 | GtkWidget *af_view; 100 | GtkWidget *af_scroll; 101 | 102 | gboolean autoscroll; 103 | } ui_t; 104 | 105 | extern ui_t ui; 106 | 107 | void ui_init(); 108 | 109 | GtkWidget* ui_bandwidth_new(); 110 | void ui_bandwidth_fill(GtkWidget*, gboolean); 111 | gboolean ui_update_title(gpointer); 112 | 113 | void ui_dialog(GtkWidget*, GtkMessageType, gchar*, gchar*, ...); 114 | void connect_button(gboolean); 115 | gint ui_antenna_switch(gint); 116 | gint ui_antenna_id(gint); 117 | void ui_antenna_update(); 118 | void ui_toggle_ps_mode(); 119 | void ui_toggle_rt_mode(); 120 | void ui_screenshot(gboolean); 121 | void ui_activate(); 122 | void ui_rotator_button_swap(); 123 | void ui_status(gint, gchar*, ...); 124 | void ui_decorations(gboolean); 125 | gboolean ui_dialog_confirm_disconnect(); 126 | void ui_interface_change(); 127 | 128 | #endif 129 | 130 | -------------------------------------------------------------------------------- /src/win32.c: -------------------------------------------------------------------------------- 1 | #ifndef _WIN32_WINNT 2 | #define _WIN32_WINNT 0x0501 3 | #endif 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "ui.h" 11 | #include "conf.h" 12 | #include "win32.h" 13 | 14 | static const char css_string[] = 15 | "* {\n" 16 | " font-family: Sans;\n" 17 | " font-size: 10pt;\n" 18 | "}\n"; 19 | 20 | #define WIN32_FONT_FILE ".\\share\\fonts\\TTF\\DejaVuSansMono.ttf" 21 | gint win32_font; 22 | 23 | 24 | void 25 | win32_init(void) 26 | { 27 | WSADATA wsaData; 28 | win32_font = AddFontResourceEx(WIN32_FONT_FILE, FR_PRIVATE, NULL); 29 | if(WSAStartup(MAKEWORD(2,2), &wsaData)) 30 | ui_dialog(NULL, 31 | GTK_MESSAGE_ERROR, 32 | "Error", "Unable to initialize Winsock"); 33 | 34 | GtkCssProvider *provider = gtk_css_provider_new(); 35 | gtk_css_provider_load_from_data(provider, css_string, -1, NULL); 36 | GdkScreen *screen = gdk_display_get_default_screen(gdk_display_get_default()); 37 | gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); 38 | } 39 | 40 | void 41 | win32_cleanup(void) 42 | { 43 | if(win32_font) 44 | RemoveFontResourceEx(WIN32_FONT_FILE, FR_PRIVATE, NULL); 45 | WSACleanup(); 46 | } 47 | 48 | gboolean 49 | win32_uri(GtkWidget *label, 50 | gchar *uri, 51 | gpointer user_data) 52 | { 53 | ShellExecute(0, "open", uri, NULL, NULL, 1); 54 | return TRUE; 55 | } 56 | 57 | gint 58 | win32_dialog_workaround(GtkDialog *dialog) 59 | { 60 | /* Always keep a dialog over the main window */ 61 | if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ui.b_ontop))) 62 | gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE); 63 | 64 | return gtk_dialog_run(dialog); 65 | } 66 | 67 | void 68 | win32_realize(GtkWidget *widget, 69 | gpointer user_data) 70 | { 71 | gboolean dark_theme = FALSE; 72 | 73 | g_object_get(gtk_settings_get_default(), 74 | "gtk-application-prefer-dark-theme", 75 | &dark_theme, NULL); 76 | 77 | if (dark_theme) 78 | win32_dark_titlebar(widget); 79 | } 80 | 81 | void 82 | win32_grab_focus(GtkWindow* window) 83 | { 84 | HWND handle; 85 | gint fg; 86 | gint app; 87 | 88 | if(gtk_window_is_active(window)) 89 | return; 90 | 91 | handle = GDK_WINDOW_HWND(gtk_widget_get_window(ui.window)); 92 | fg = GetWindowThreadProcessId(GetForegroundWindow(), NULL); 93 | app = GetCurrentThreadId(); 94 | 95 | if(IsIconic(handle)) 96 | { 97 | ShowWindow(handle, SW_RESTORE); 98 | } 99 | else if(fg != app) 100 | { 101 | AttachThreadInput(fg, app, TRUE); 102 | BringWindowToTop(handle); 103 | ShowWindow(handle, SW_SHOW); 104 | AttachThreadInput(fg, app, FALSE); 105 | } 106 | else 107 | { 108 | gtk_window_present(window); 109 | } 110 | } 111 | 112 | void 113 | win32_dark_titlebar(GtkWidget *widget) 114 | { 115 | const DWORD dark_mode = 20; 116 | const DWORD dark_mode_pre20h1 = 19; 117 | const BOOL value = TRUE; 118 | GdkWindow *window = gtk_widget_get_window(widget); 119 | 120 | if (window == NULL) 121 | return; 122 | 123 | HWND handle = GDK_WINDOW_HWND(window); 124 | if (!SUCCEEDED(DwmSetWindowAttribute(handle, dark_mode, &value, sizeof(value)))) 125 | DwmSetWindowAttribute(handle, dark_mode_pre20h1, &value, sizeof(value)); 126 | } 127 | 128 | gchar* 129 | strsep(gchar **string, 130 | gchar *del) 131 | { 132 | gchar *start = *string; 133 | gchar *p = (start ? strpbrk(start, del) : NULL); 134 | 135 | if(!p) 136 | { 137 | *string = NULL; 138 | } 139 | else 140 | { 141 | *p = '\0'; 142 | *string = p + 1; 143 | } 144 | return start; 145 | } 146 | -------------------------------------------------------------------------------- /src/antpatt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "ui.h" 4 | #include "tuner.h" 5 | 6 | struct xdrgtk_antpatt 7 | { 8 | GIOChannel *channel; 9 | gboolean active; 10 | }; 11 | 12 | static struct xdrgtk_antpatt antpatt = 13 | { 14 | .channel = NULL, 15 | .active = FALSE 16 | }; 17 | 18 | static void antpatt_callback(GPid, gint, gpointer); 19 | static void antpatt_write(GIOChannel*, gboolean, const gchar*, ...); 20 | static void antpatt_start(void); 21 | static void antpatt_stop(void); 22 | 23 | static void antpatt_ui_set_running(gboolean); 24 | static void antpatt_ui_set_active(gboolean); 25 | 26 | 27 | void 28 | antpatt_toggle(void) 29 | { 30 | gchar *command[] = { "antpatt", (conf.dark_theme ? "-id" : "-i"), NULL }; 31 | GSpawnFlags flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD; 32 | GPid pid; 33 | gint stream; 34 | 35 | if (antpatt.channel) 36 | { 37 | antpatt_ui_set_running(TRUE); 38 | if (!antpatt.active) 39 | { 40 | antpatt_start(); 41 | } 42 | else 43 | { 44 | antpatt_stop(); 45 | } 46 | return; 47 | } 48 | 49 | if (!g_spawn_async_with_pipes(NULL, command, NULL, flags, 0, NULL, &pid, &stream, NULL, NULL, NULL)) 50 | { 51 | antpatt_ui_set_running(FALSE); 52 | ui_dialog(ui.window, 53 | GTK_MESSAGE_ERROR, 54 | "Antenna pattern", 55 | "Failed to run antpatt.\nMake sure it is installed or available the same directory as xdr-gtk."); 56 | return; 57 | } 58 | 59 | #ifdef G_OS_WIN32 60 | antpatt.channel = g_io_channel_win32_new_fd(stream); 61 | #else 62 | antpatt.channel = g_io_channel_unix_new(stream); 63 | #endif 64 | if (antpatt.channel) 65 | { 66 | antpatt.active = FALSE; 67 | g_child_watch_add(pid, (GChildWatchFunc)antpatt_callback, NULL); 68 | g_io_channel_set_close_on_unref(antpatt.channel, TRUE); 69 | antpatt_ui_set_running(TRUE); 70 | } 71 | else 72 | { 73 | g_spawn_close_pid(pid); 74 | } 75 | } 76 | 77 | void 78 | antpatt_push(gfloat level) 79 | { 80 | if (antpatt.channel && antpatt.active) 81 | antpatt_write(antpatt.channel, TRUE, "PUSH %.2f\n", level); 82 | } 83 | 84 | static void 85 | antpatt_callback(GPid pid, 86 | gint status, 87 | gpointer user_data) 88 | { 89 | if (antpatt.channel) 90 | { 91 | g_io_channel_unref(antpatt.channel); 92 | antpatt.channel = NULL; 93 | } 94 | 95 | g_spawn_close_pid(pid); 96 | 97 | antpatt_ui_set_active(FALSE); 98 | antpatt_ui_set_running(FALSE); 99 | } 100 | 101 | static void 102 | antpatt_write(GIOChannel *channel, 103 | gboolean flush, 104 | const gchar *format, 105 | ...) 106 | { 107 | va_list args; 108 | gchar *msg; 109 | gsize written; 110 | 111 | va_start(args, format); 112 | msg = g_strdup_vprintf(format, args); 113 | va_end(args); 114 | 115 | g_io_channel_write_chars(channel, msg, strlen(msg), &written, NULL); 116 | 117 | if (flush) 118 | g_io_channel_flush(channel, NULL); 119 | 120 | g_free(msg); 121 | } 122 | 123 | static void 124 | antpatt_start(void) 125 | { 126 | if (antpatt.channel) 127 | { 128 | antpatt_write(antpatt.channel, FALSE, "START\n"); 129 | antpatt_write(antpatt.channel, TRUE, "FREQ %d\n", tuner_get_freq()); 130 | antpatt_ui_set_active(TRUE); 131 | antpatt.active = TRUE; 132 | } 133 | } 134 | 135 | static void 136 | antpatt_stop(void) 137 | { 138 | if (antpatt.channel) 139 | { 140 | antpatt_write(antpatt.channel, TRUE, "STOP\n"); 141 | antpatt_ui_set_active(FALSE); 142 | antpatt.active = FALSE; 143 | } 144 | } 145 | 146 | static void 147 | antpatt_ui_set_running(gboolean is_active) 148 | { 149 | g_signal_handlers_block_by_func(G_OBJECT(ui.b_pattern), GINT_TO_POINTER(antpatt_toggle), NULL); 150 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_pattern), is_active); 151 | g_signal_handlers_unblock_by_func(G_OBJECT(ui.b_pattern), GINT_TO_POINTER(antpatt_toggle), NULL); 152 | } 153 | 154 | static void 155 | antpatt_ui_set_active(gboolean state) 156 | { 157 | if (state) 158 | gtk_style_context_add_class(gtk_widget_get_style_context(ui.b_pattern), "xdr-action"); 159 | else 160 | gtk_style_context_remove_class(gtk_widget_get_style_context(ui.b_pattern), "xdr-action"); 161 | } 162 | -------------------------------------------------------------------------------- /src/tuner-filters.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "tuner.h" 4 | #include "conf.h" 5 | 6 | static const gint filters_legacy[] = 7 | { 8 | 15, 9 | 14, 10 | 13, 11 | 12, 12 | 11, 13 | 10, 14 | 9, 15 | 8, 16 | 7, 17 | 6, 18 | 5, 19 | 4, 20 | 3, 21 | 29, 22 | 2, 23 | 28, 24 | 1, 25 | 26, 26 | 0, 27 | 24, 28 | 23, 29 | 22, 30 | 21, 31 | 20, 32 | 19, 33 | 18, 34 | 17, 35 | 16, 36 | 31, 37 | -1 38 | }; 39 | 40 | static const gint filters_bw_fm_xdr[] = 41 | { 42 | /* FM */ 43 | 309000, 44 | 298000, 45 | 281000, 46 | 263000, 47 | 246000, 48 | 229000, 49 | 211000, 50 | 194000, 51 | 177000, 52 | 159000, 53 | 142000, 54 | 125000, 55 | 108000, 56 | 95000, 57 | 90000, 58 | 83000, 59 | 73000, 60 | 63000, 61 | 55000, 62 | 48000, 63 | 42000, 64 | 36000, 65 | 32000, 66 | 27000, 67 | 24000, 68 | 20000, 69 | 17000, 70 | 15000, 71 | 9000, 72 | 0 73 | }; 74 | 75 | static const gint filters_bw_am_xdr[] = 76 | { 77 | 38600, 78 | 37300, 79 | 35100, 80 | 32900, 81 | 30800, 82 | 28600, 83 | 26400, 84 | 24300, 85 | 22100, 86 | 19900, 87 | 17800, 88 | 15600, 89 | 13500, 90 | 11800, 91 | 11300, 92 | 10400, 93 | 9100, 94 | 7900, 95 | 6900, 96 | 6000, 97 | 5200, 98 | 4600, 99 | 3900, 100 | 3400, 101 | 2900, 102 | 2500, 103 | 2200, 104 | 1900, 105 | 1100, 106 | 0 107 | }; 108 | 109 | static const gint filters_bw_fm_tef[] = 110 | { 111 | 311000, 112 | 287000, 113 | 254000, 114 | 236000, 115 | 217000, 116 | 200000, 117 | 184000, 118 | 168000, 119 | 151000, 120 | 133000, 121 | 114000, 122 | 97000, 123 | 84000, 124 | 72000, 125 | 64000, 126 | 56000, 127 | 0 128 | }; 129 | 130 | static const gint filters_bw_am_tef[] = 131 | { 132 | 8000, 133 | 6000, 134 | 4000, 135 | 3000, 136 | 0 137 | }; 138 | 139 | #define FILTERS_LEGACY_COUNT (sizeof(filters_legacy) / sizeof(filters_legacy[0])) 140 | G_STATIC_ASSERT(sizeof(filters_legacy) == sizeof(filters_bw_fm_xdr)); 141 | G_STATIC_ASSERT(sizeof(filters_legacy) == sizeof(filters_bw_am_xdr)); 142 | 143 | 144 | static const gint* 145 | tuner_filter_get_array(gint *len_out) 146 | { 147 | if (tuner.mode == MODE_FM) 148 | { 149 | if (conf.tef668x_mode) 150 | { 151 | *len_out = sizeof(filters_bw_fm_tef) / sizeof(filters_bw_fm_tef[0]); 152 | return filters_bw_fm_tef; 153 | } 154 | else 155 | { 156 | *len_out = sizeof(filters_bw_fm_xdr) / sizeof(filters_bw_fm_xdr[0]); 157 | return filters_bw_fm_xdr; 158 | } 159 | } 160 | else if (tuner.mode == MODE_AM) 161 | { 162 | if (conf.tef668x_mode) 163 | { 164 | *len_out = sizeof(filters_bw_am_tef) / sizeof(filters_bw_am_tef[0]); 165 | return filters_bw_am_tef; 166 | } 167 | else 168 | { 169 | *len_out = sizeof(filters_bw_am_xdr) / sizeof(filters_bw_am_xdr[0]); 170 | return filters_bw_am_xdr; 171 | } 172 | } 173 | 174 | *len_out = 0; 175 | return NULL; 176 | } 177 | 178 | gint 179 | tuner_filter_from_index(gint index) 180 | { 181 | /* For legacy 'F' message */ 182 | if(index >= 0 && index < FILTERS_LEGACY_COUNT) 183 | return filters_legacy[index]; 184 | 185 | return -1; /* unknown -> adaptive */ 186 | } 187 | 188 | gint 189 | tuner_filter_bw(gint filter) 190 | { 191 | /* From legacy 'F' message */ 192 | /* Always use the legacy table with XDR bandwidths */ 193 | for (gint i = 0; i < FILTERS_LEGACY_COUNT; i++) 194 | { 195 | if (filters_legacy[i] == filter) 196 | { 197 | if (tuner.mode == MODE_FM) 198 | return filters_bw_fm_xdr[i]; 199 | else if (tuner.mode == MODE_AM) 200 | return filters_bw_am_xdr[i]; 201 | break; 202 | } 203 | } 204 | 205 | return 0; /* unknown bandwidth */ 206 | } 207 | 208 | gint 209 | tuner_filter_bw_from_index(gint index) 210 | { 211 | gint count; 212 | const gint *array = tuner_filter_get_array(&count); 213 | 214 | if (index < 0 || 215 | index >= count) 216 | { 217 | return 0; 218 | } 219 | 220 | return array[index]; 221 | } 222 | 223 | gint 224 | tuner_filter_index_from_bw(gint bw) 225 | { 226 | gint count; 227 | const gint *array = tuner_filter_get_array(&count); 228 | 229 | gint index = count - 1; 230 | gint min = G_MAXINT; 231 | gint diff, i; 232 | 233 | if (bw > 0) 234 | { 235 | for (i = 0; i < count - 1; i++) 236 | { 237 | diff = abs(array[i] - bw); 238 | if (min > diff) 239 | { 240 | min = diff; 241 | index = i; 242 | } 243 | } 244 | } 245 | 246 | return index; 247 | } 248 | 249 | gint 250 | tuner_filter_count() 251 | { 252 | gint count; 253 | tuner_filter_get_array(&count); 254 | 255 | return count; 256 | } 257 | -------------------------------------------------------------------------------- /src/conf.h: -------------------------------------------------------------------------------- 1 | #ifndef XDR_CONF_H_ 2 | #define XDR_CONF_H_ 3 | #include 4 | #include 5 | 6 | #define PRESETS 12 7 | #define ANT_COUNT 4 8 | #define HOST_HISTORY_LEN 5 9 | 10 | enum Action 11 | { 12 | ACTION_NONE, 13 | ACTION_ACTIVATE, 14 | ACTION_SCREENSHOT 15 | }; 16 | 17 | enum Signal_Unit 18 | { 19 | UNIT_DBF, 20 | UNIT_DBM, 21 | UNIT_DBUV 22 | }; 23 | 24 | enum Signal_Display 25 | { 26 | SIGNAL_NONE, 27 | SIGNAL_GRAPH, 28 | SIGNAL_BAR 29 | }; 30 | 31 | enum Signal_Mode 32 | { 33 | GRAPH_DEFAULT, 34 | GRAPH_RESET 35 | }; 36 | 37 | enum RDS_Mode 38 | { 39 | RDS, 40 | RBDS 41 | }; 42 | 43 | enum RDS_Err_Correction 44 | { 45 | NO_ERR_CORR, 46 | SMALL_ERR_CORR, 47 | LARGE_ERR_CORR 48 | }; 49 | 50 | typedef struct settings 51 | { 52 | /* Window */ 53 | gint win_x; 54 | gint win_y; 55 | 56 | /* Connection */ 57 | gboolean network; 58 | gchar *serial; 59 | gchar **host; 60 | guint16 port; 61 | gchar *password; 62 | 63 | /* Tuner settings */ 64 | gint volume; 65 | gboolean rfgain; 66 | gboolean ifgain; 67 | gint agc; 68 | gint deemphasis; 69 | 70 | /* Interface */ 71 | gint initial_freq; 72 | gboolean utc; 73 | gboolean auto_connect; 74 | gboolean fm_10k_steps; 75 | gboolean mw_10k_steps; 76 | gboolean disconnect_confirm; 77 | gboolean auto_reconnect; 78 | enum Action event_action; 79 | gboolean hide_decorations; 80 | gboolean hide_interference; 81 | gboolean hide_radiotext; 82 | gboolean hide_statusbar; 83 | gboolean restore_position; 84 | gboolean grab_focus; 85 | gboolean title_tuner_info; 86 | gint title_tuner_mode; 87 | gboolean accessibility; 88 | gboolean horizontal_af; 89 | gboolean dark_theme; 90 | gboolean extended_frequency; 91 | gboolean tef668x_mode; 92 | 93 | /* Graph */ 94 | gdouble signal_offset; 95 | enum Signal_Unit signal_unit; 96 | enum Signal_Display signal_display; 97 | enum Signal_Mode signal_mode; 98 | gint signal_height; 99 | gboolean signal_scroll; 100 | gboolean signal_grid; 101 | gboolean signal_avg; 102 | GdkRGBA color_mono; 103 | GdkRGBA color_stereo; 104 | GdkRGBA color_rds; 105 | GdkRGBA color_mono_dark; 106 | GdkRGBA color_stereo_dark; 107 | GdkRGBA color_rds_dark; 108 | 109 | /* RDS */ 110 | enum RDS_Mode rds_pty_set; 111 | gboolean rds_reset; 112 | gint rds_reset_timeout; 113 | gboolean rds_extended_check; 114 | enum RDS_Err_Correction rds_ps_info_error; 115 | enum RDS_Err_Correction rds_ps_data_error; 116 | gboolean rds_ps_progressive; 117 | gboolean rds_ps_prog_override; 118 | enum RDS_Err_Correction rds_rt_info_error; 119 | enum RDS_Err_Correction rds_rt_data_error; 120 | gboolean rds_rt_progressive; 121 | gboolean rds_rt_prog_override; 122 | 123 | /* Antenna */ 124 | gboolean ant_show_alignment; 125 | gboolean ant_swap_rotator; 126 | gint ant_count; 127 | gboolean ant_clear_rds; 128 | gboolean ant_auto_switch; 129 | gint ant_start[ANT_COUNT]; 130 | gint ant_stop[ANT_COUNT]; 131 | gint ant_offset[ANT_COUNT]; 132 | char **ant_name; 133 | 134 | /* Logs */ 135 | gint rdsspy_port; 136 | gboolean rdsspy_auto; 137 | gboolean rdsspy_run; 138 | gchar *rdsspy_exec; 139 | gboolean srcp; 140 | gint srcp_port; 141 | gboolean rds_logging; 142 | gboolean replace_spaces; 143 | gchar *log_dir; 144 | gchar *screen_dir; 145 | 146 | /* Keyboard */ 147 | guint key_tune_up; 148 | guint key_tune_down; 149 | guint key_tune_fine_up; 150 | guint key_tune_fine_down; 151 | guint key_tune_jump_up; 152 | guint key_tune_jump_down; 153 | guint key_tune_back; 154 | guint key_tune_reset; 155 | guint key_screenshot; 156 | guint key_bw_up; 157 | guint key_bw_down; 158 | guint key_bw_auto; 159 | guint key_rotate_cw; 160 | guint key_rotate_ccw; 161 | guint key_switch_antenna; 162 | guint key_rds_ps_mode; 163 | guint key_scan_toggle; 164 | guint key_scan_prev; 165 | guint key_scan_next; 166 | guint key_stereo_toggle; 167 | guint key_mode_toggle; 168 | 169 | /* Presets */ 170 | gint presets[PRESETS]; 171 | 172 | /* Scheduler */ 173 | gsize scheduler_n; 174 | gint *scheduler_freqs; 175 | gint *scheduler_timeouts; 176 | gint scheduler_default_timeout; 177 | 178 | /* Spectral scan */ 179 | gint scan_x; 180 | gint scan_y; 181 | gint scan_width; 182 | gint scan_height; 183 | gint scan_start; 184 | gint scan_end; 185 | gint scan_step; 186 | gint scan_bw; 187 | gboolean scan_continuous; 188 | gboolean scan_relative; 189 | gboolean scan_peakhold; 190 | gboolean scan_mark_tuned; 191 | gboolean scan_update; 192 | GList *scan_marks; 193 | } settings_t; 194 | 195 | extern settings_t conf; 196 | 197 | void conf_init(const gchar*); 198 | void conf_write(); 199 | 200 | void conf_uniq_int_list_add(GList**, gint); 201 | void conf_uniq_int_list_toggle(GList**, gint); 202 | void conf_uniq_int_list_remove(GList**, gint); 203 | void conf_uniq_int_list_clear_range(GList**, gint, gint); 204 | void conf_uniq_int_list_clear(GList**); 205 | 206 | void conf_update_string_const(gchar**, const gchar*); 207 | void conf_update_string(gchar**, gchar*); 208 | 209 | void conf_add_host(const gchar*); 210 | 211 | #endif 212 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "log.h" 8 | #include "tuner.h" 9 | #include "conf.h" 10 | #include "ui.h" 11 | 12 | static FILE *logfp = NULL; 13 | static GString *ps_buff = NULL; 14 | static GString *rt_buff[2] = {NULL, NULL}; 15 | static gboolean ps_buff_error; 16 | static gchar *default_log_path = "." PATH_SEP "logs"; 17 | 18 | static gboolean log_prepare(); 19 | static void log_timestamp(); 20 | 21 | static gboolean 22 | log_prepare() 23 | { 24 | gchar t[16], t2[16], path[100]; 25 | gchar *directory; 26 | time_t tt; 27 | 28 | if(!conf.rds_logging) 29 | { 30 | log_cleanup(); 31 | return FALSE; 32 | } 33 | 34 | if(logfp) 35 | { 36 | return TRUE; 37 | } 38 | 39 | directory = ((conf.log_dir && strlen(conf.log_dir)) ? conf.log_dir : default_log_path); 40 | 41 | ps_buff = ps_buff ? g_string_truncate(ps_buff, 0) : g_string_new(""); 42 | rt_buff[0] = rt_buff[0] ? g_string_truncate(rt_buff[0], 0) : g_string_new(""); 43 | rt_buff[1] = rt_buff[1] ? g_string_truncate(rt_buff[1], 0) : g_string_new(""); 44 | ps_buff_error = TRUE; 45 | 46 | g_snprintf(path, sizeof(path), "%s" PATH_SEP, directory); 47 | g_mkdir(path, 0755); 48 | 49 | tt = time(NULL); 50 | strftime(t, sizeof(t), "%Y-%m-%d", (conf.utc)?gmtime(&tt):localtime(&tt)); 51 | 52 | if (conf.utc) 53 | strftime(t2, sizeof(t2), "%H%M%SZ", gmtime(&tt)); 54 | else 55 | strftime(t2, sizeof(t2), "%H%M%S", localtime(&tt)); 56 | 57 | g_snprintf(path, sizeof(path), "%s" PATH_SEP "%s" PATH_SEP, directory, t); 58 | g_mkdir(path, 0755); 59 | 60 | g_snprintf(path, sizeof(path), "%s" PATH_SEP "%s" PATH_SEP "%d-%s.txt", directory, t, tuner_get_freq(), t2); 61 | logfp = g_fopen(path, "w"); 62 | 63 | if(!logfp) 64 | ui_status(2000, "Failed to create a log. Check logging directory in settings."); 65 | 66 | return (logfp != NULL); 67 | } 68 | 69 | static void 70 | log_timestamp() 71 | { 72 | gchar t[32]; 73 | time_t tt; 74 | 75 | if(logfp) 76 | { 77 | tt = time(NULL); 78 | 79 | if (conf.utc) 80 | strftime(t, sizeof(t), "%Y-%m-%d %H:%M:%SZ", gmtime(&tt)); 81 | else 82 | strftime(t, sizeof(t), "%Y-%m-%d %H:%M:%S", localtime(&tt)); 83 | 84 | fprintf(logfp, "%s\t", t); 85 | } 86 | } 87 | 88 | void 89 | log_cleanup() 90 | { 91 | if(logfp) 92 | { 93 | fclose(logfp); 94 | logfp = NULL; 95 | } 96 | } 97 | 98 | void 99 | log_pi(gint pi, 100 | gint err_level) 101 | { 102 | if(!log_prepare()) 103 | return; 104 | 105 | log_timestamp(); 106 | fprintf(logfp, "PI\t%04X", pi); 107 | if(err_level) 108 | fprintf(logfp, "\t"); 109 | while(err_level--) 110 | fprintf(logfp, "?"); 111 | fprintf(logfp, "%s", LOG_NL); 112 | } 113 | 114 | void 115 | log_af(const gchar *af) 116 | { 117 | if(!log_prepare()) 118 | return; 119 | 120 | log_timestamp(); 121 | fprintf(logfp, "AF\t%s%s", af, LOG_NL); 122 | } 123 | 124 | void 125 | log_ps(const gchar *ps, 126 | const gboolean error) 127 | { 128 | gchar *tmp; 129 | 130 | if(!log_prepare()) 131 | return; 132 | 133 | /* Check whether the PS string is different from a last saved one */ 134 | if(!strcmp(ps, ps_buff->str) && error == ps_buff_error) 135 | return; 136 | 137 | ps_buff = g_string_assign(ps_buff, ps); 138 | ps_buff_error = error; 139 | 140 | log_timestamp(); 141 | 142 | if(conf.replace_spaces) 143 | { 144 | tmp = replace_spaces(ps); 145 | fprintf(logfp, "PS\t%s", tmp); 146 | g_free(tmp); 147 | } 148 | else 149 | { 150 | fprintf(logfp, "PS\t%s", ps); 151 | } 152 | 153 | if(error) 154 | fprintf(logfp, "\t?%s", LOG_NL); 155 | else 156 | fprintf(logfp, "%s", LOG_NL); 157 | } 158 | 159 | void 160 | log_rt(guint8 i, 161 | const gchar *rt) 162 | { 163 | gchar *tmp; 164 | 165 | if(!log_prepare()) 166 | return; 167 | 168 | /* Check whether the RT string is different from a last saved one */ 169 | if(!strcmp(rt, rt_buff[i]->str)) 170 | return; 171 | 172 | rt_buff[i] = g_string_assign(rt_buff[i], rt); 173 | 174 | log_timestamp(); 175 | 176 | if(conf.replace_spaces) 177 | { 178 | tmp = replace_spaces(rt); 179 | fprintf(logfp, "RT%d\t%s%s", i+1, tmp, LOG_NL); 180 | g_free(tmp); 181 | } 182 | else 183 | { 184 | fprintf(logfp, "RT%d\t%s%s", i+1, rt, LOG_NL); 185 | } 186 | } 187 | 188 | void 189 | log_pty(const gchar *pty) 190 | { 191 | if(!log_prepare()) 192 | return; 193 | 194 | log_timestamp(); 195 | fprintf(logfp, "PTY\t%s%s", pty, LOG_NL); 196 | } 197 | 198 | void 199 | log_ecc(const gchar *ecc, 200 | guint ecc_raw) 201 | { 202 | if(!log_prepare()) 203 | return; 204 | 205 | log_timestamp(); 206 | if(!strcmp(ecc, "??")) 207 | fprintf(logfp, "ECC\t?? (%02X)%s", ecc_raw, LOG_NL); 208 | else 209 | fprintf(logfp, "ECC\t%s%s", ecc, LOG_NL); 210 | } 211 | 212 | 213 | void 214 | log_ct(const char *datetime) 215 | { 216 | if(!log_prepare()) 217 | return; 218 | 219 | log_timestamp(); 220 | 221 | fprintf(logfp, "CT\t%s%s", datetime, LOG_NL); 222 | } 223 | 224 | gchar* 225 | replace_spaces(const gchar *str) 226 | { 227 | gchar *new_str = g_strdup(str); 228 | size_t i; 229 | for(i=0; i 2 | #include 3 | #ifdef G_OS_WIN32 4 | #ifndef _WIN32_WINNT 5 | #define _WIN32_WINNT 0x0501 6 | #endif 7 | #include 8 | #include 9 | #include 10 | #include "win32.h" 11 | #else 12 | #include 13 | #include 14 | #include 15 | #include 16 | #endif 17 | #include "rdsspy.h" 18 | #include "ui.h" 19 | #include "conf.h" 20 | #include "tuner-conn.h" 21 | 22 | static gint rdsspy_socket = -1; 23 | static gint rdsspy_client = -1; 24 | static gboolean rdsspy_child = 0; 25 | static GPid rdsspy_pid; 26 | 27 | static gboolean rdsspy_init(gint); 28 | static gpointer rdsspy_server(gpointer); 29 | static gboolean rdsspy_toggle_button(gpointer); 30 | static void rdsspy_child_watch(GPid, gint, gpointer); 31 | 32 | 33 | void 34 | rdsspy_toggle() 35 | { 36 | GError *error = NULL; 37 | gchar *command[] = { conf.rdsspy_exec, NULL }; 38 | 39 | if(rdsspy_is_up()) 40 | { 41 | if(rdsspy_child) 42 | { 43 | #ifdef G_OS_WIN32 44 | TerminateProcess(rdsspy_pid, 0); 45 | #else 46 | kill(rdsspy_pid, SIGTERM); 47 | #endif 48 | } 49 | else 50 | { 51 | rdsspy_stop(); 52 | } 53 | } 54 | else 55 | { 56 | if(rdsspy_init(conf.rdsspy_port) && conf.rdsspy_run && strlen(conf.rdsspy_exec)) 57 | { 58 | if(!g_spawn_async(NULL, command, NULL, G_SPAWN_DO_NOT_REAP_CHILD, 0, NULL, &rdsspy_pid, &error)) 59 | { 60 | ui_dialog(ui.window, 61 | GTK_MESSAGE_ERROR, 62 | "RDS Spy link", 63 | "%s", 64 | error->message); 65 | g_error_free(error); 66 | } 67 | else 68 | { 69 | rdsspy_child = TRUE; 70 | g_child_watch_add(rdsspy_pid, (GChildWatchFunc)rdsspy_child_watch, NULL); 71 | } 72 | } 73 | } 74 | } 75 | 76 | gboolean 77 | rdsspy_is_up() 78 | { 79 | return (rdsspy_socket >= 0); 80 | } 81 | 82 | gboolean 83 | rdsspy_is_connected() 84 | { 85 | return (rdsspy_socket >= 0 && rdsspy_client >= 0); 86 | } 87 | 88 | void 89 | rdsspy_stop() 90 | { 91 | shutdown(rdsspy_socket, 2); 92 | closesocket(rdsspy_socket); 93 | if(rdsspy_client >= 0) 94 | shutdown(rdsspy_client, 2); 95 | } 96 | 97 | static gboolean 98 | rdsspy_init(gint port) 99 | { 100 | rdsspy_socket = socket(AF_INET, SOCK_STREAM, 0); 101 | if(rdsspy_socket < 0) 102 | { 103 | ui_dialog(ui.window, 104 | GTK_MESSAGE_ERROR, 105 | "RDS Spy link", 106 | "rdsspy_init: socket"); 107 | return FALSE; 108 | } 109 | 110 | #ifndef G_OS_WIN32 111 | int on = 1; 112 | if(setsockopt(rdsspy_socket, SOL_SOCKET, SO_REUSEADDR, (const char*) &on, sizeof(on)) < 0) 113 | { 114 | ui_dialog(ui.window, 115 | GTK_MESSAGE_ERROR, 116 | "RDS Spy link", 117 | "rdsspy_init: SO_REUSEADDR"); 118 | return FALSE; 119 | } 120 | #endif 121 | 122 | struct sockaddr_in addr; 123 | memset((char*)&addr, 0, sizeof(addr)); 124 | addr.sin_family = AF_INET; 125 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 126 | addr.sin_port = htons(port); 127 | 128 | if(bind(rdsspy_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) 129 | { 130 | ui_dialog(ui.window, 131 | GTK_MESSAGE_ERROR, 132 | "RDS Spy link", 133 | "Failed to bind to a port: %d.\nIt may be already in use by another application.", 134 | port); 135 | closesocket(rdsspy_socket); 136 | rdsspy_socket = -1; 137 | rdsspy_toggle_button(GINT_TO_POINTER(FALSE)); 138 | return FALSE; 139 | } 140 | listen(rdsspy_socket, 4); 141 | 142 | rdsspy_toggle_button(GINT_TO_POINTER(TRUE)); 143 | g_thread_unref(g_thread_new("rdsspy", rdsspy_server, NULL)); 144 | return TRUE; 145 | } 146 | 147 | static gpointer 148 | rdsspy_server(gpointer nothing) 149 | { 150 | char c; 151 | while((rdsspy_client = accept(rdsspy_socket, (struct sockaddr*)NULL, NULL)) >= 0) 152 | { 153 | fd_set input; 154 | FD_ZERO(&input); 155 | FD_SET(rdsspy_client, &input); 156 | while(select(rdsspy_client+1, &input, NULL, NULL, NULL) > 0) 157 | { 158 | if(recv(rdsspy_client, &c, 1, 0) <= 0) 159 | { 160 | break; 161 | } 162 | } 163 | closesocket(rdsspy_client); 164 | rdsspy_client = -1; 165 | } 166 | rdsspy_socket = -1; 167 | g_idle_add(rdsspy_toggle_button, GINT_TO_POINTER(FALSE)); 168 | return NULL; 169 | } 170 | 171 | static gboolean 172 | rdsspy_toggle_button(gpointer is_active) 173 | { 174 | g_signal_handlers_block_by_func(G_OBJECT(ui.b_rdsspy), GINT_TO_POINTER(rdsspy_toggle), NULL); 175 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_rdsspy), GPOINTER_TO_INT(is_active)); 176 | g_signal_handlers_unblock_by_func(G_OBJECT(ui.b_rdsspy), GINT_TO_POINTER(rdsspy_toggle), NULL); 177 | return FALSE; 178 | } 179 | 180 | static void 181 | rdsspy_child_watch(GPid pid, 182 | gint status, 183 | gpointer data) 184 | { 185 | rdsspy_child = FALSE; 186 | g_spawn_close_pid(pid); 187 | if(conf.rdsspy_run && rdsspy_is_up()) 188 | rdsspy_stop(); 189 | } 190 | 191 | void 192 | rdsspy_reset() 193 | { 194 | if(!rdsspy_is_connected()) 195 | return; 196 | 197 | gchar out[15]; 198 | g_snprintf(out, sizeof(out), "G:\r\nRESET\r\n\r\n"); 199 | send(rdsspy_client, out, strlen(out), 0); 200 | } 201 | 202 | void 203 | rdsspy_send(guint *data, 204 | guint errors) 205 | { 206 | if(!rdsspy_is_connected()) 207 | return; 208 | 209 | gchar groups[4][5]; 210 | gchar out[25]; 211 | 212 | /* Block A */ 213 | if ((errors & 192) == 0) 214 | { 215 | g_snprintf(groups[0], 5, "%04X", data[0]); 216 | } 217 | else 218 | { 219 | g_snprintf(groups[0], 5, "----"); 220 | } 221 | 222 | /* Block B */ 223 | if((errors & 48) == 0) 224 | { 225 | g_snprintf(groups[1], 5, "%04X", data[1]); 226 | } 227 | else 228 | { 229 | g_snprintf(groups[1], 5, "----"); 230 | } 231 | 232 | /* Block C */ 233 | if((errors & 12) == 0) 234 | { 235 | g_snprintf(groups[2], 5, "%04X", data[2]); 236 | } 237 | else 238 | { 239 | g_snprintf(groups[2], 5, "----"); 240 | } 241 | 242 | /* Block D */ 243 | if((errors & 3) == 0) 244 | { 245 | g_snprintf(groups[3], 5, "%04X", data[3]); 246 | } 247 | else 248 | { 249 | g_snprintf(groups[3], 5, "----"); 250 | } 251 | 252 | g_snprintf(out, sizeof(out), "G:\r\n%s%s%s%s\r\n\r\n", groups[0], groups[1], groups[2], groups[3]); 253 | send(rdsspy_client, out, strlen(out), 0); 254 | } 255 | -------------------------------------------------------------------------------- /src/ui-audio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "ui-audio.h" 4 | #include "ui-tuner-set.h" 5 | 6 | static const gchar* const icons_volume[] = 7 | { 8 | "audio-volume-muted-symbolic", 9 | "audio-volume-high-symbolic", 10 | "audio-volume-low-symbolic", 11 | "audio-volume-medium-symbolic", 12 | NULL 13 | }; 14 | 15 | static const gchar* const icon_squelch_off[] = 16 | { 17 | "xdr-gtk-squelch-off", 18 | NULL 19 | }; 20 | 21 | static const gchar* const icon_squelch_st[] = 22 | { 23 | "xdr-gtk-squelch-st", 24 | NULL 25 | }; 26 | 27 | static const gchar* const icon_squelch_on[] = 28 | { 29 | "xdr-gtk-squelch-on", 30 | NULL 31 | }; 32 | 33 | static gboolean volume_tooltip(GtkWidget*, gint, gint, gboolean, GtkTooltip*, gpointer); 34 | static gboolean volume_click(GtkWidget*, GdkEventButton*); 35 | static void volume_text(GtkScaleButton*, gdouble, gpointer); 36 | static gboolean squelch_tooltip(GtkWidget*, gint, gint, gboolean, GtkTooltip*, gpointer); 37 | static gboolean squelch_click(GtkWidget*, GdkEventButton*); 38 | static void squelch_text(GtkScaleButton*, gdouble, gpointer); 39 | static void squelch_icon(GtkScaleButton*, gdouble, gpointer); 40 | 41 | 42 | GtkWidget* 43 | volume_init(gint value) 44 | { 45 | GtkWidget *scale; 46 | GtkWidget *area; 47 | GtkWidget *label; 48 | 49 | scale = gtk_scale_button_new(GTK_ICON_SIZE_LARGE_TOOLBAR, 0.0, 100.0, 2.5, (const gchar **)icons_volume); 50 | gtk_widget_set_can_focus(GTK_WIDGET(scale), FALSE); 51 | gtk_widget_set_has_tooltip(scale, TRUE); 52 | gtk_scale_button_set_value(GTK_SCALE_BUTTON(scale), value); 53 | 54 | label = gtk_label_new(NULL); 55 | volume_text(GTK_SCALE_BUTTON(scale), value, (gpointer)label); 56 | area = gtk_bin_get_child(GTK_BIN(gtk_scale_button_get_popup(GTK_SCALE_BUTTON(scale)))); 57 | gtk_box_pack_end(GTK_BOX(area), label, TRUE, TRUE, 3); 58 | gtk_widget_show(label); 59 | 60 | g_signal_connect(scale, "value-changed", G_CALLBACK(tuner_set_volume), NULL); 61 | g_signal_connect(scale, "query-tooltip", G_CALLBACK(volume_tooltip), NULL); 62 | g_signal_connect(scale, "button-press-event", G_CALLBACK(volume_click), NULL); 63 | g_signal_connect(scale, "value-changed", G_CALLBACK(volume_text), label); 64 | return scale; 65 | } 66 | 67 | static gboolean 68 | volume_tooltip(GtkWidget *button, 69 | gint x, 70 | gint y, 71 | gboolean keyboard_mode, 72 | GtkTooltip *tooltip, 73 | gpointer user_data) 74 | { 75 | GtkScaleButton *scale_button = GTK_SCALE_BUTTON(button); 76 | GtkAdjustment *adjustment = gtk_scale_button_get_adjustment(scale_button); 77 | gchar *str; 78 | gint percent; 79 | 80 | percent = (gint)(100. * gtk_scale_button_get_value(scale_button) / (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_lower (adjustment)) + .5); 81 | str = g_strdup_printf("Volume: %d%%", percent); 82 | gtk_tooltip_set_text(tooltip, str); 83 | g_free(str); 84 | return TRUE; 85 | } 86 | 87 | static gboolean 88 | volume_click(GtkWidget *widget, 89 | GdkEventButton *event) 90 | { 91 | if(event->type == GDK_BUTTON_PRESS && event->button == 3) // right click 92 | { 93 | if(gtk_scale_button_get_value(GTK_SCALE_BUTTON(widget)) > 0) 94 | gtk_scale_button_set_value(GTK_SCALE_BUTTON(widget), 0.0); 95 | else 96 | gtk_scale_button_set_value(GTK_SCALE_BUTTON(widget), 100.0); 97 | return TRUE; 98 | } 99 | return FALSE; 100 | } 101 | 102 | static void 103 | volume_text(GtkScaleButton *widget, 104 | gdouble value, 105 | gpointer label) 106 | { 107 | gchar *str; 108 | str = g_strdup_printf("%d", (gint)round(value)); 109 | gtk_label_set_markup(GTK_LABEL(label), str); 110 | g_free(str); 111 | } 112 | 113 | GtkWidget* 114 | squelch_init(gint value) 115 | { 116 | GtkWidget *scale; 117 | GtkWidget *area; 118 | GtkWidget *label; 119 | 120 | scale = gtk_scale_button_new(GTK_ICON_SIZE_LARGE_TOOLBAR, -1.0, 100.0, 0.5, (const gchar **)icon_squelch_off); 121 | gtk_widget_set_can_focus(GTK_WIDGET(scale), FALSE); 122 | gtk_widget_set_has_tooltip(scale, TRUE); 123 | gtk_scale_button_set_value(GTK_SCALE_BUTTON(scale), value); 124 | 125 | label = gtk_label_new(NULL); 126 | squelch_text(GTK_SCALE_BUTTON(scale), value, (gpointer)label); 127 | area = gtk_bin_get_child(GTK_BIN(gtk_scale_button_get_popup(GTK_SCALE_BUTTON(scale)))); 128 | gtk_box_pack_end(GTK_BOX(area), label, TRUE, TRUE, 3); 129 | gtk_widget_show(label); 130 | 131 | g_signal_connect(scale, "value-changed", G_CALLBACK(squelch_icon), NULL); 132 | g_signal_connect(scale, "value-changed", G_CALLBACK(tuner_set_squelch), NULL); 133 | g_signal_connect(scale, "query-tooltip", G_CALLBACK(squelch_tooltip), NULL); 134 | g_signal_connect(scale, "button-press-event", G_CALLBACK(squelch_click), NULL); 135 | g_signal_connect(scale, "value-changed", G_CALLBACK(squelch_text), label); 136 | return scale; 137 | } 138 | 139 | static gboolean 140 | squelch_tooltip(GtkWidget *button, 141 | gint x, 142 | gint y, 143 | gboolean keyboard_mode, 144 | GtkTooltip *tooltip, 145 | gpointer user_data) 146 | { 147 | gchar *str; 148 | gint value = (int)round(gtk_scale_button_get_value(GTK_SCALE_BUTTON(button))); 149 | 150 | if(value >= 0) 151 | { 152 | str = g_strdup_printf("Squelch: %d dBf", value); 153 | gtk_tooltip_set_text(tooltip, str); 154 | g_free(str); 155 | } 156 | else 157 | { 158 | gtk_tooltip_set_text(tooltip, "Squelch: stereo"); 159 | } 160 | 161 | return TRUE; 162 | } 163 | 164 | static gboolean 165 | squelch_click(GtkWidget *widget, 166 | GdkEventButton *event) 167 | { 168 | if(event->type == GDK_BUTTON_PRESS && event->button == 3) // right click 169 | { 170 | gtk_scale_button_set_value(GTK_SCALE_BUTTON(widget), 0.0); 171 | return TRUE; 172 | } 173 | return FALSE; 174 | } 175 | 176 | static void 177 | squelch_text(GtkScaleButton *widget, 178 | gdouble value, 179 | gpointer label) 180 | { 181 | gchar *str; 182 | gint v = (gint)round(value); 183 | 184 | if(v >= 0) 185 | { 186 | str = g_strdup_printf("%d", v); 187 | gtk_label_set_markup(GTK_LABEL(label), str); 188 | g_free(str); 189 | } 190 | else 191 | { 192 | gtk_label_set_markup(GTK_LABEL(label), "ST"); 193 | } 194 | } 195 | 196 | static void 197 | squelch_icon(GtkScaleButton *button, 198 | gdouble value, 199 | gpointer user_data) 200 | { 201 | gint v = (gint)round(value); 202 | if(v < 0) 203 | gtk_scale_button_set_icons(button, (const gchar **)icon_squelch_st); 204 | else if(v == 0) 205 | gtk_scale_button_set_icons(button, (const gchar **)icon_squelch_off); 206 | else 207 | gtk_scale_button_set_icons(button, (const gchar **)icon_squelch_on); 208 | } 209 | -------------------------------------------------------------------------------- /icons/scalable/apps/xdr-gtk.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 37 | 40 | 44 | 48 | 52 | 56 | 60 | 64 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/tuner-conn.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "ui-connect.h" 4 | #include "tuner-conn.h" 5 | #include "tuner.h" 6 | #ifdef G_OS_WIN32 7 | #ifndef _WIN32_WINNT 8 | #define _WIN32_WINNT 0x0501 9 | #endif 10 | #include 11 | #include 12 | #include 13 | #include "win32.h" 14 | #else 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #endif 25 | 26 | #define SOCKET_TCP_KEEPCNT 2 27 | #define SOCKET_TCP_KEEPINTVL 10 28 | #define SOCKET_TCP_KEEPIDLE 30 29 | 30 | gint 31 | tuner_open_serial(const gchar *serial_port, 32 | gintptr *fd) 33 | { 34 | gchar path[100]; 35 | #ifdef G_OS_WIN32 36 | HANDLE serial; 37 | DCB dcbSerialParams = {0}; 38 | 39 | g_snprintf(path, sizeof(path), "\\\\.\\%s", serial_port); 40 | serial = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); 41 | if(serial == INVALID_HANDLE_VALUE) 42 | return CONN_SERIAL_FAIL_OPEN; 43 | if(!GetCommState(serial, &dcbSerialParams)) 44 | { 45 | CloseHandle(serial); 46 | return CONN_SERIAL_FAIL_PARM_R; 47 | } 48 | dcbSerialParams.BaudRate = CBR_115200; 49 | dcbSerialParams.ByteSize = 8; 50 | dcbSerialParams.StopBits = ONESTOPBIT; 51 | dcbSerialParams.Parity = NOPARITY; 52 | if(!SetCommState(serial, &dcbSerialParams)) 53 | { 54 | CloseHandle(serial); 55 | return CONN_SERIAL_FAIL_PARM_W; 56 | } 57 | *fd = (gintptr)serial; 58 | #else 59 | struct termios options; 60 | g_snprintf(path, sizeof(path), "/dev/%s", serial_port); 61 | *fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY); 62 | if(*fd < 0) 63 | return CONN_SERIAL_FAIL_OPEN; 64 | fcntl(*fd, F_SETFL, 0); 65 | tcflush(*fd, TCIOFLUSH); 66 | if(tcgetattr(*fd, &options)) 67 | { 68 | close(*fd); 69 | return CONN_SERIAL_FAIL_PARM_R; 70 | } 71 | if(cfsetispeed(&options, B115200) || cfsetospeed(&options, B115200)) 72 | { 73 | close(*fd); 74 | return CONN_SERIAL_FAIL_SPEED; 75 | } 76 | options.c_iflag &= ~(BRKINT | ICRNL | IXON | IMAXBEL); 77 | options.c_iflag |= IGNBRK; 78 | options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG | IEXTEN | ECHOK | ECHOCTL | ECHOKE); 79 | options.c_oflag &= ~(OPOST | ONLCR); 80 | options.c_oflag |= NOFLSH; 81 | options.c_cflag |= CS8; 82 | options.c_cflag &= ~(CRTSCTS); 83 | if(tcsetattr(*fd, TCSANOW, &options)) 84 | { 85 | close(*fd); 86 | return CONN_SERIAL_FAIL_PARM_W; 87 | } 88 | #endif 89 | return CONN_SUCCESS; 90 | } 91 | 92 | gpointer 93 | tuner_open_socket(gpointer ptr) 94 | { 95 | conn_t *data = (conn_t*)ptr; 96 | struct addrinfo hints = {0}, *result; 97 | struct timeval timeout = {0}; 98 | fd_set input; 99 | gchar salt[SOCKET_SALT_LEN+1], msg[42]; 100 | GChecksum *sha1; 101 | 102 | hints.ai_family = AF_INET; 103 | hints.ai_socktype = SOCK_STREAM; 104 | 105 | /* Resolve the hostname */ 106 | data->state = CONN_SOCKET_STATE_RESOLV; 107 | g_idle_add(connection_socket_callback_info, data); 108 | if(getaddrinfo(data->hostname, data->port, &hints, &result)) 109 | { 110 | data->state = CONN_SOCKET_FAIL_RESOLV; 111 | g_idle_add(connection_socket_callback, data); 112 | return NULL; 113 | } 114 | 115 | if(data->canceled) 116 | { 117 | freeaddrinfo(result); 118 | g_idle_add(connection_socket_callback, data); 119 | return NULL; 120 | } 121 | 122 | data->state = CONN_SOCKET_STATE_CONN; 123 | g_idle_add(connection_socket_callback_info, data); 124 | data->socketfd = socket(result->ai_family, result->ai_socktype, result->ai_protocol); 125 | if(connect(data->socketfd, result->ai_addr, result->ai_addrlen) < 0 || data->canceled) 126 | { 127 | closesocket(data->socketfd); 128 | freeaddrinfo(result); 129 | data->state = CONN_SOCKET_FAIL_CONN; 130 | g_idle_add(connection_socket_callback, data); 131 | return NULL; 132 | } 133 | freeaddrinfo(result); 134 | 135 | data->state = CONN_SOCKET_STATE_AUTH; 136 | g_idle_add(connection_socket_callback_info, data); 137 | FD_ZERO(&input); 138 | FD_SET(data->socketfd, &input); 139 | timeout.tv_sec = SOCKET_AUTH_TIMEOUT; 140 | /* Wait SOCKET_AUTH_TIMEOUT seconds for the salt */ 141 | if(select(data->socketfd+1, &input, NULL, NULL, &timeout) <= 0 || data->canceled) 142 | { 143 | closesocket(data->socketfd); 144 | data->state = CONN_SOCKET_FAIL_AUTH; 145 | g_idle_add(connection_socket_callback, data); 146 | return NULL; 147 | } 148 | 149 | /* Receive SOCKET_SALT_LENGTH+1 bytes (with \n) */ 150 | if(recv(data->socketfd, salt, SOCKET_SALT_LEN+1, 0) != SOCKET_SALT_LEN+1 || data->canceled) 151 | { 152 | closesocket(data->socketfd); 153 | data->state = CONN_SOCKET_FAIL_AUTH; 154 | g_idle_add(connection_socket_callback, data); 155 | return NULL; 156 | } 157 | 158 | /* Calculate the SHA1 checksum of salt and password concatenation */ 159 | sha1 = g_checksum_new(G_CHECKSUM_SHA1); 160 | g_checksum_update(sha1, (guchar*)salt, SOCKET_SALT_LEN); 161 | if(data->password && strlen(data->password)) 162 | { 163 | g_checksum_update(sha1, (guchar*)data->password, strlen(data->password)); 164 | } 165 | g_snprintf(msg, sizeof(msg), "%s\n", g_checksum_get_string(sha1)); 166 | g_checksum_free(sha1); 167 | 168 | /* Send the hash */ 169 | if(!tuner_write_socket(data->socketfd, msg, strlen(msg)) || data->canceled) 170 | { 171 | closesocket(data->socketfd); 172 | data->state = CONN_SOCKET_FAIL_WRITE; 173 | g_idle_add(connection_socket_callback, data); 174 | return NULL; 175 | } 176 | 177 | #ifdef G_OS_WIN32 178 | DWORD ret; 179 | struct tcp_keepalive ka = 180 | { 181 | .onoff = 1, 182 | .keepaliveinterval = SOCKET_TCP_KEEPINTVL * SOCKET_TCP_KEEPCNT * 1000 / 10, 183 | .keepalivetime = SOCKET_TCP_KEEPIDLE * 1000 184 | }; 185 | WSAIoctl(data->socketfd, SIO_KEEPALIVE_VALS, &ka, sizeof(ka), NULL, 0, &ret, NULL, NULL); 186 | #else 187 | 188 | #if !defined(SOL_TCP) && defined(IPPROTO_TCP) 189 | #define SOL_TCP IPPROTO_TCP 190 | #endif 191 | #if !defined(TCP_KEEPIDLE) && defined(TCP_KEEPALIVE) 192 | #define TCP_KEEPIDLE TCP_KEEPALIVE 193 | #endif 194 | 195 | gint opt = 1; 196 | if(setsockopt(data->socketfd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)) >= 0) 197 | { 198 | opt = SOCKET_TCP_KEEPCNT; 199 | setsockopt(data->socketfd, IPPROTO_TCP, TCP_KEEPCNT, &opt, sizeof(opt)); 200 | 201 | opt = SOCKET_TCP_KEEPINTVL; 202 | setsockopt(data->socketfd, IPPROTO_TCP, TCP_KEEPINTVL, &opt, sizeof(opt)); 203 | 204 | opt = SOCKET_TCP_KEEPIDLE; 205 | setsockopt(data->socketfd, IPPROTO_TCP, TCP_KEEPIDLE, &opt, sizeof(opt)); 206 | } 207 | #endif 208 | 209 | data->state = CONN_SUCCESS; 210 | g_idle_add(connection_socket_callback, data); 211 | return NULL; 212 | } 213 | 214 | conn_t* 215 | conn_new(const gchar *hostname, 216 | const gchar *port, 217 | const gchar *password) 218 | { 219 | conn_t *ptr = g_new(conn_t, 1); 220 | ptr->hostname = g_strdup(hostname); 221 | ptr->port = g_strdup(port); 222 | ptr->password = g_strdup(password); 223 | ptr->canceled = FALSE; 224 | ptr->state = CONN_SOCKET_STATE_UNDEF; 225 | return ptr; 226 | } 227 | 228 | void 229 | conn_free(conn_t *ptr) 230 | { 231 | g_free(ptr->hostname); 232 | g_free(ptr->port); 233 | g_free(ptr->password); 234 | g_free(ptr); 235 | } 236 | 237 | -------------------------------------------------------------------------------- /src/ui-tuner-set.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ui.h" 3 | #include "ui-tuner-set.h" 4 | #include "ui-tuner-update.h" 5 | #include "tuner.h" 6 | #include "conf.h" 7 | #include "stationlist.h" 8 | 9 | static void tuner_modify_frequency_full(guint, guint, guint); 10 | 11 | void 12 | tuner_set_frequency(gint freq) 13 | { 14 | static gint freq_waiting = -1; 15 | static gint64 last_request = 0; 16 | gint64 now = g_get_real_time(); 17 | gchar buffer[16]; 18 | gint real_freq; 19 | gint ant; 20 | 21 | ant = ui_antenna_id(freq); 22 | real_freq = freq + tuner.offset[ant]; 23 | 24 | if((now-last_request) < 1000000 && 25 | real_freq == freq_waiting && 26 | real_freq != tuner.freq) 27 | return; 28 | 29 | g_snprintf(buffer, sizeof(buffer), "T%d", real_freq); 30 | tuner_write(tuner.thread, buffer); 31 | 32 | ui_antenna_switch(freq); 33 | 34 | freq_waiting = real_freq; 35 | last_request = now; 36 | } 37 | 38 | void 39 | tuner_set_frequency_prev() 40 | { 41 | tuner_set_frequency(tuner.prevfreq - tuner.offset[tuner.prevantenna]); 42 | } 43 | 44 | void 45 | tuner_set_mode(gint mode) 46 | { 47 | gchar buffer[3]; 48 | g_snprintf(buffer, sizeof(buffer), "M%d", mode); 49 | tuner_write(tuner.thread, buffer); 50 | } 51 | 52 | void 53 | tuner_set_bandwidth() 54 | { 55 | gchar buffer[16]; 56 | gint index = gtk_combo_box_get_active(GTK_COMBO_BOX(ui.c_bw)); 57 | 58 | if (!conf.tef668x_mode) 59 | { 60 | /* Indexes are only valid for XDR bandwidths */ 61 | g_snprintf(buffer, sizeof(buffer), "F%d", tuner_filter_from_index(index)); 62 | tuner_write(tuner.thread, buffer); 63 | } 64 | 65 | g_snprintf(buffer, sizeof(buffer), "W%d", tuner_filter_bw_from_index(index)); 66 | tuner_write(tuner.thread, buffer); 67 | 68 | tuner.last_set_bandwidth = g_get_real_time() / 1000; 69 | } 70 | 71 | void 72 | tuner_set_deemphasis() 73 | { 74 | gchar buffer[3]; 75 | conf.deemphasis = gtk_combo_box_get_active(GTK_COMBO_BOX(ui.c_deemph)); 76 | g_snprintf(buffer, sizeof(buffer), "D%d", conf.deemphasis); 77 | tuner_write(tuner.thread, buffer); 78 | tuner.last_set_deemph = g_get_real_time() / 1000; 79 | } 80 | 81 | void 82 | tuner_set_volume() 83 | { 84 | gchar buffer[5]; 85 | conf.volume = lround(gtk_scale_button_get_value(GTK_SCALE_BUTTON(ui.volume))); 86 | g_snprintf(buffer, sizeof(buffer), "Y%d", conf.volume); 87 | tuner_write(tuner.thread, buffer); 88 | tuner.last_set_volume = g_get_real_time() / 1000; 89 | } 90 | 91 | void 92 | tuner_set_squelch() 93 | { 94 | gchar buffer[5]; 95 | g_snprintf(buffer, sizeof(buffer), "Q%ld", lround(gtk_scale_button_get_value(GTK_SCALE_BUTTON(ui.squelch)))); 96 | tuner_write(tuner.thread, buffer); 97 | tuner.last_set_squelch = g_get_real_time() / 1000; 98 | } 99 | 100 | void 101 | tuner_set_antenna() 102 | { 103 | gchar buffer[3]; 104 | gint antenna = gtk_combo_box_get_active(GTK_COMBO_BOX(ui.c_ant)); 105 | g_snprintf(buffer, sizeof(buffer), "Z%d", (antenna >=0 ? antenna : 0)); 106 | tuner_write(tuner.thread, buffer); 107 | tuner.last_set_ant = g_get_real_time() / 1000; 108 | } 109 | 110 | void 111 | tuner_set_agc() 112 | { 113 | gchar buffer[3]; 114 | conf.agc = gtk_combo_box_get_active(GTK_COMBO_BOX(ui.c_agc)); 115 | g_snprintf(buffer, sizeof(buffer), "A%d", conf.agc); 116 | tuner_write(tuner.thread, buffer); 117 | tuner.last_set_agc = g_get_real_time() / 1000; 118 | } 119 | 120 | void 121 | tuner_set_gain() 122 | { 123 | gchar buffer[4]; 124 | conf.rfgain = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ui.x_rf)); 125 | conf.ifgain = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ui.x_if)); 126 | g_snprintf(buffer, sizeof(buffer), "G%02d", conf.rfgain * 10 + conf.ifgain); 127 | tuner_write(tuner.thread, buffer); 128 | tuner.last_set_gain = g_get_real_time() / 1000; 129 | } 130 | 131 | void 132 | tuner_set_alignment() 133 | { 134 | gchar buffer[5]; 135 | g_snprintf(buffer, sizeof(buffer), "V%ld", lround(gtk_adjustment_get_value(GTK_ADJUSTMENT(ui.adj_align)))); 136 | tuner_write(tuner.thread, buffer); 137 | tuner.last_set_daa = g_get_real_time() / 1000; 138 | } 139 | 140 | void 141 | tuner_set_rotator(gpointer user_data) 142 | { 143 | gboolean cw = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ui.b_cw)); 144 | gboolean ccw = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ui.b_ccw)); 145 | gchar buffer[3]; 146 | gint state = GPOINTER_TO_INT(user_data); 147 | 148 | if(!cw && !ccw) 149 | { 150 | state = 0; 151 | } 152 | else if(state == 1) 153 | { 154 | if(ccw) 155 | { 156 | g_signal_handlers_block_by_func(G_OBJECT(ui.b_ccw), GINT_TO_POINTER(tuner_set_rotator), GINT_TO_POINTER(2)); 157 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_ccw), FALSE); 158 | g_signal_handlers_unblock_by_func(G_OBJECT(ui.b_ccw), GINT_TO_POINTER(tuner_set_rotator), GINT_TO_POINTER(2)); 159 | } 160 | } 161 | else if(state == 2) 162 | { 163 | if(cw) 164 | { 165 | g_signal_handlers_block_by_func(G_OBJECT(ui.b_cw), GINT_TO_POINTER(tuner_set_rotator), GINT_TO_POINTER(1)); 166 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_cw), FALSE); 167 | g_signal_handlers_unblock_by_func(G_OBJECT(ui.b_cw), GINT_TO_POINTER(tuner_set_rotator), GINT_TO_POINTER(1)); 168 | } 169 | } 170 | 171 | g_snprintf(buffer, sizeof(buffer), "C%d", state); 172 | tuner_write(tuner.thread, buffer); 173 | tuner.last_set_rotator = g_get_real_time() / 1000; 174 | } 175 | 176 | void 177 | tuner_set_forced_mono(gboolean value) 178 | { 179 | if(value) 180 | tuner_write(tuner.thread, "B1"); 181 | else 182 | tuner_write(tuner.thread, "B0"); 183 | } 184 | 185 | void 186 | tuner_set_stereo_test() 187 | { 188 | tuner_write(tuner.thread, "N"); 189 | tuner.last_set_pilot = g_get_real_time() / 1000; 190 | } 191 | 192 | void 193 | tuner_set_sampling_interval(gint interval, gboolean mode) 194 | { 195 | gchar buffer[10]; 196 | g_snprintf(buffer, sizeof(buffer), "I%d,%d", interval, mode); 197 | tuner_write(tuner.thread, buffer); 198 | } 199 | 200 | void 201 | tuner_modify_frequency(guint mode) 202 | { 203 | if(tuner_get_freq() <= 300) 204 | tuner_modify_frequency_full(99, 9, mode); 205 | else if(tuner_get_freq() <= 1900) 206 | tuner_modify_frequency_full((conf.mw_10k_steps?100:99), (conf.mw_10k_steps?10:9), mode); 207 | else if(tuner_get_freq() <= 30000) 208 | tuner_modify_frequency_full(1900, 10, mode); 209 | else if(tuner_get_freq() >= 65750 && tuner_get_freq() <= 74000) 210 | tuner_modify_frequency_full(65750, 30, mode); 211 | else 212 | tuner_modify_frequency_full(0, 100, mode); 213 | } 214 | 215 | static void 216 | tuner_modify_frequency_full(guint base_freq, 217 | guint step, 218 | guint mode) 219 | { 220 | gint m; 221 | if(((tuner_get_freq()-base_freq) % step) == 0) 222 | { 223 | if(mode == TUNER_FREQ_MODIFY_UP) 224 | tuner_set_frequency(tuner_get_freq()+step); 225 | else if(mode == TUNER_FREQ_MODIFY_DOWN) 226 | tuner_set_frequency(tuner_get_freq()-step); 227 | else if(mode == TUNER_FREQ_MODIFY_RESET) 228 | tuner_set_frequency(tuner_get_freq()); 229 | } 230 | else 231 | { 232 | m = (tuner_get_freq()-base_freq) % step; 233 | if(mode == TUNER_FREQ_MODIFY_UP || 234 | (mode == TUNER_FREQ_MODIFY_RESET && m >= (step/2))) 235 | { 236 | tuner_set_frequency(base_freq+((tuner_get_freq()-base_freq)/step*step)+step); 237 | } 238 | else if(mode == TUNER_FREQ_MODIFY_DOWN || 239 | (mode == TUNER_FREQ_MODIFY_RESET && m < (step/2))) 240 | { 241 | tuner_set_frequency(base_freq+((tuner_get_freq()-base_freq)/step*step)); 242 | } 243 | } 244 | } 245 | 246 | -------------------------------------------------------------------------------- /src/tuner-callbacks.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "tuner-callbacks.h" 5 | #include "ui-tuner-update.h" 6 | #include "tuner.h" 7 | #include "conf.h" 8 | 9 | #include "rdsspy.h" 10 | 11 | #define DEFAULT_SAMPLING_INTERVAL 66 12 | 13 | gboolean 14 | tuner_ready(gpointer data) 15 | { 16 | tuner.ready = TRUE; 17 | tuner.guest = GPOINTER_TO_INT(data); 18 | return FALSE; 19 | } 20 | 21 | gboolean 22 | tuner_unauthorized(gpointer data) 23 | { 24 | ui_unauthorized(); 25 | return FALSE; 26 | } 27 | 28 | gboolean 29 | tuner_disconnect(gpointer data) 30 | { 31 | tuner_clear_all(); /* tuner.thread = NULL */ 32 | g_free(data); /* free tuner_thread_t */ 33 | 34 | rdsspy_reset(); 35 | return FALSE; 36 | } 37 | 38 | gboolean 39 | tuner_freq(gpointer data) 40 | { 41 | gint freq = GPOINTER_TO_INT(data); 42 | if(freq != tuner.freq || 43 | (tuner.prevantenna != tuner.antenna && 44 | tuner.offset[tuner.prevantenna] != tuner.offset[tuner.antenna])) 45 | { 46 | tuner.prevfreq = tuner.freq; 47 | tuner.prevantenna = tuner.antenna; 48 | tuner.freq = freq; 49 | } 50 | 51 | 52 | tuner_clear_signal(); 53 | tuner_clear_rds(); 54 | ui_update_freq(); 55 | 56 | tuner.signal = NAN; 57 | tuner.signal_max = NAN; 58 | tuner.signal_sum = 0.0; 59 | tuner.signal_samples = 0; 60 | tuner.ready_tuned = TRUE; 61 | 62 | rdsspy_reset(); 63 | return FALSE; 64 | } 65 | 66 | gboolean 67 | tuner_daa(gpointer data) 68 | { 69 | tuner.daa = GPOINTER_TO_INT(data); 70 | return FALSE; 71 | } 72 | 73 | gboolean 74 | tuner_signal(gpointer data) 75 | { 76 | tuner_signal_t *signal = (tuner_signal_t*)data; 77 | if(tuner.ready_tuned) 78 | { 79 | if(tuner.rds_timeout) 80 | tuner.rds_timeout--; 81 | 82 | tuner.signal = signal->value; 83 | if(isnan(tuner.signal_max) || tuner.signal > tuner.signal_max) 84 | tuner.signal_max = tuner.signal; 85 | tuner.signal_sum += tuner.signal; 86 | tuner.signal_samples++; 87 | ui_update_signal(); 88 | 89 | tuner.stereo = signal->stereo & SIGNAL_STEREO; 90 | tuner.forced_mono = signal->stereo & SIGNAL_FORCED_MONO; 91 | ui_update_stereo_flag(); 92 | ui_update_rds_flag(); 93 | } 94 | g_free(data); 95 | return FALSE; 96 | } 97 | 98 | gboolean 99 | tuner_cci(gpointer data) 100 | { 101 | tuner.cci = GPOINTER_TO_INT(data); 102 | ui_update_cci(); 103 | return FALSE; 104 | } 105 | 106 | gboolean 107 | tuner_aci(gpointer data) 108 | { 109 | tuner.aci = GPOINTER_TO_INT(data); 110 | ui_update_aci(); 111 | return FALSE; 112 | } 113 | 114 | gboolean 115 | tuner_pi(gpointer data) 116 | { 117 | gint pi = GPOINTER_TO_INT(data) & 0xFFFF; 118 | gint err_level = (GPOINTER_TO_INT(data) & 0x30000) >> 16; 119 | gint interval = (tuner.sampling_interval ? tuner.sampling_interval : DEFAULT_SAMPLING_INTERVAL); 120 | 121 | if(err_level > tuner.rds_pi_err_level && 122 | tuner.rds_pi != pi) 123 | return FALSE; 124 | 125 | /* RDS stream: 1187.5 bps 126 | * One group: 104 bits (each has PI code) */ 127 | tuner.rds_timeout = ceil(1000 * 104 / 1187.5 / interval) + 1; 128 | tuner.rds_reset_timer = g_get_real_time(); 129 | 130 | tuner.rds_pi = pi; 131 | if(err_level < tuner.rds_pi_err_level) 132 | tuner.rds_pi_err_level = err_level; 133 | 134 | ui_update_pi(); 135 | return FALSE; 136 | } 137 | 138 | gboolean 139 | tuner_rds_legacy(gpointer msg_ptr) 140 | { 141 | gchar *msg = (gchar*)msg_ptr; 142 | guint data[4], errors; 143 | 144 | for (guint i = 1; i < 4; i++) 145 | { 146 | gchar hex[5]; 147 | strncpy(hex, msg+(i-1)*4, 4); 148 | hex[4] = 0; 149 | sscanf(hex, "%x", &data[i]); 150 | } 151 | 152 | sscanf(msg+12, "%x", &errors); 153 | g_free(msg); 154 | 155 | guint errors_corrected = 0; 156 | 157 | if (!tuner.rds_timeout) 158 | { 159 | errors_corrected |= (0x03 << 6); 160 | } 161 | 162 | errors_corrected |= (errors & 0x03) << 4; 163 | errors_corrected |= (errors & 0x0C); 164 | errors_corrected |= (errors & 0x30) >> 4; 165 | 166 | char *new_format; 167 | new_format = g_strdup_printf("%04X%04X%04X%04X%02X", 168 | (tuner.rds_pi >= 0 ? tuner.rds_pi : 0), 169 | data[1], 170 | data[2], 171 | data[3], 172 | errors_corrected); 173 | 174 | tuner_rds_new(new_format); 175 | return FALSE; 176 | } 177 | 178 | gboolean 179 | tuner_rds_new(gpointer msg_ptr) 180 | { 181 | gchar *msg = (gchar*)msg_ptr; 182 | guint data[4], errors, i; 183 | 184 | for (i = 0; i < 4; i++) 185 | { 186 | gchar hex[5]; 187 | strncpy(hex, msg+i*4, 4); 188 | hex[4] = 0; 189 | sscanf(hex, "%x", &data[i]); 190 | } 191 | 192 | sscanf(msg+16, "%x", &errors); 193 | 194 | rdsparser_parse_string(tuner.rds, msg); 195 | rdsspy_send(data, errors); 196 | 197 | g_free(msg); 198 | return FALSE; 199 | } 200 | 201 | gboolean 202 | tuner_scan(gpointer data) 203 | { 204 | tuner_scan_t *scan = (tuner_scan_t*)data; 205 | gint i; 206 | gint offset = tuner_get_offset(); 207 | 208 | if(offset) 209 | for(i=0; ilen; i++) 210 | scan->signals[i].freq -= offset; 211 | 212 | ui_update_scan(scan); 213 | return FALSE; 214 | } 215 | 216 | gboolean 217 | tuner_pilot(gpointer data) 218 | { 219 | ui_update_pilot(GPOINTER_TO_INT(data)); 220 | return FALSE; 221 | } 222 | 223 | gboolean 224 | tuner_volume(gpointer data) 225 | { 226 | tuner.volume = GPOINTER_TO_INT(data); 227 | return FALSE; 228 | } 229 | 230 | gboolean 231 | tuner_agc(gpointer data) 232 | { 233 | tuner.agc = GPOINTER_TO_INT(data); 234 | return FALSE; 235 | } 236 | 237 | gboolean 238 | tuner_deemphasis(gpointer data) 239 | { 240 | tuner.deemphasis = GPOINTER_TO_INT(data); 241 | return FALSE; 242 | } 243 | 244 | gboolean 245 | tuner_antenna(gpointer data) 246 | { 247 | tuner.antenna = GPOINTER_TO_INT(data); 248 | if(conf.ant_clear_rds) 249 | { 250 | tuner_clear_rds(); 251 | rdsspy_reset(); 252 | } 253 | tuner_clear_signal(); 254 | ui_update_freq(); 255 | return FALSE; 256 | } 257 | 258 | gboolean 259 | tuner_event(gpointer data) 260 | { 261 | ui_action(); 262 | return FALSE; 263 | } 264 | 265 | gboolean 266 | tuner_gain(gpointer data) 267 | { 268 | gint gain = GPOINTER_TO_INT(data); 269 | tuner.rfgain = (gain == 10 || gain == 11); 270 | tuner.ifgain = (gain == 1 || gain == 11); 271 | tuner_clear_signal(); 272 | return FALSE; 273 | } 274 | 275 | gboolean 276 | tuner_mode(gpointer data) 277 | { 278 | tuner.mode = GPOINTER_TO_INT(data); 279 | tuner_clear_signal(); 280 | tuner_clear_rds(); 281 | ui_update_mode(); 282 | tuner.bandwidth = 0; 283 | ui_update_bandwidth(); 284 | return FALSE; 285 | } 286 | 287 | gboolean 288 | tuner_filter(gpointer data) 289 | { 290 | tuner.bandwidth = tuner_filter_bw(GPOINTER_TO_INT(data)); 291 | ui_update_bandwidth(); 292 | return FALSE; 293 | } 294 | 295 | gboolean 296 | tuner_bandwidth(gpointer data) 297 | { 298 | tuner.bandwidth = GPOINTER_TO_INT(data); 299 | ui_update_bandwidth(); 300 | return FALSE; 301 | } 302 | 303 | gboolean 304 | tuner_squelch(gpointer data) 305 | { 306 | tuner.squelch = GPOINTER_TO_INT(data); 307 | return FALSE; 308 | } 309 | 310 | gboolean 311 | tuner_rotator(gpointer data) 312 | { 313 | gint rotator = GPOINTER_TO_INT(data); 314 | tuner.rotator = abs(rotator); 315 | tuner.rotator_waiting = (rotator < 0); 316 | ui_update_rotator(); 317 | return FALSE; 318 | } 319 | 320 | gboolean 321 | tuner_sampling_interval(gpointer data) 322 | { 323 | tuner.sampling_interval = GPOINTER_TO_INT(data); 324 | return FALSE; 325 | } 326 | 327 | gboolean 328 | tuner_online(gpointer data) 329 | { 330 | tuner.online = GPOINTER_TO_INT(data); 331 | if(!tuner.ready && tuner.online > 1) 332 | tuner.send_settings = FALSE; 333 | return FALSE; 334 | } 335 | 336 | gboolean 337 | tuner_online_guests(gpointer data) 338 | { 339 | tuner.online_guests = GPOINTER_TO_INT(data); 340 | return FALSE; 341 | } 342 | -------------------------------------------------------------------------------- /src/ui-signal.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "ui.h" 5 | #include "tuner.h" 6 | #include "conf.h" 7 | #include "ui-signal.h" 8 | 9 | #define GRAPH_FONT_SIZE 12 10 | 11 | #define GRAPH_OFFSET_LEFT 23 12 | #define GRAPH_OFFSET_TOP (GRAPH_FONT_SIZE/2) 13 | #define GRAPH_OFFSET_DBM 7 14 | 15 | #define GRAPH_SCALE_LINE_LENGTH 5 16 | 17 | typedef struct signal_sample 18 | { 19 | gfloat value; 20 | gboolean rds; 21 | gboolean stereo; 22 | gint freq; 23 | } signal_sample_t; 24 | 25 | typedef struct signal_buffer 26 | { 27 | signal_sample_t *data; 28 | gint len; 29 | gint pos; 30 | } signal_buffer_t; 31 | 32 | signal_buffer_t s; 33 | 34 | static GdkRGBA graph_color_border; 35 | static GdkRGBA graph_color_scale; 36 | 37 | static const double grid_pattern[] = {1.0, 1.0}; 38 | static gint grid_pattern_len = sizeof(grid_pattern) / sizeof(grid_pattern[0]); 39 | 40 | static gboolean graph_draw(GtkWidget*, cairo_t*, gpointer); 41 | static gboolean graph_click(GtkWidget*, GdkEventButton*, gpointer); 42 | 43 | void 44 | signal_init() 45 | { 46 | gdk_rgba_parse(&graph_color_border, "#646464"); 47 | gdk_rgba_parse(&graph_color_scale, "#AAAAAA"); 48 | 49 | s.len = (gtk_widget_get_allocated_width(ui.graph))-GRAPH_OFFSET_LEFT; 50 | s.data = g_new(signal_sample_t, s.len); 51 | 52 | gtk_widget_add_events(ui.graph, GDK_BUTTON_PRESS_MASK); 53 | g_signal_connect(ui.graph, "draw", G_CALLBACK(graph_draw), NULL); 54 | g_signal_connect(ui.graph, "button-press-event", G_CALLBACK(graph_click), NULL); 55 | 56 | signal_resize(); 57 | signal_clear(); 58 | signal_display(); 59 | } 60 | 61 | void 62 | signal_resize() 63 | { 64 | gtk_widget_set_size_request(ui.graph, -1, conf.signal_height+2*GRAPH_OFFSET_TOP); 65 | } 66 | 67 | static gboolean 68 | graph_draw(GtkWidget *widget, 69 | cairo_t *cr, 70 | gpointer nothing) 71 | { 72 | gdouble step; 73 | gint current_value; 74 | gdouble position; 75 | gdouble last_position; 76 | gint max = G_MININT; 77 | gint min = G_MAXINT; 78 | gint i, curr, prev, next; 79 | gdouble value; 80 | gchar text[10]; 81 | gint offset_left = GRAPH_OFFSET_LEFT + (conf.signal_unit == UNIT_DBM ? GRAPH_OFFSET_DBM : 0); 82 | gint draw_count = (gtk_widget_get_allocated_width(widget)) - offset_left; 83 | 84 | if(draw_count > s.len) 85 | draw_count = s.len; 86 | 87 | curr = s.pos; 88 | for(i=0; i max) 93 | max = ceil(signal_level(s.data[curr].value)); 94 | if(signal_level(s.data[curr].value) < min) 95 | min = floor(signal_level(s.data[curr].value)); 96 | curr = ((curr==0) ? (s.len-1) : curr-1); 97 | } 98 | 99 | if(max == G_MININT) 100 | return FALSE; 101 | 102 | if((max - min) == 0) 103 | max++; 104 | if((max - min) == 1) 105 | min--; 106 | 107 | GdkRGBA color; 108 | gtk_style_context_get_color(gtk_widget_get_style_context(widget), GTK_STATE_FLAG_NORMAL, &color); 109 | 110 | GdkRGBA grid_color = color; 111 | grid_color.alpha = 0.25; 112 | 113 | cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); 114 | cairo_set_line_width(cr, 1.0); 115 | cairo_select_font_face(cr, "DejaVu Sans Mono", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 116 | cairo_set_font_size(cr, GRAPH_FONT_SIZE); 117 | 118 | step = conf.signal_height / (gdouble)(max - min); 119 | current_value = max; 120 | position = GRAPH_OFFSET_TOP+0.5; 121 | last_position = GRAPH_FONT_SIZE * -1.0; 122 | while(position <= GRAPH_OFFSET_TOP+conf.signal_height+1.0) 123 | { 124 | if(last_position + GRAPH_FONT_SIZE + 1.0 < position) 125 | { 126 | /* Draw a tick */ 127 | gdk_cairo_set_source_rgba(cr, &graph_color_scale); 128 | cairo_move_to(cr, offset_left-0.5-GRAPH_SCALE_LINE_LENGTH/2.0, position); 129 | cairo_line_to(cr, offset_left-0.5+GRAPH_SCALE_LINE_LENGTH/2.0, position); 130 | cairo_stroke(cr); 131 | 132 | /* Draw a grid */ 133 | if(conf.signal_grid && current_value != min) 134 | { 135 | cairo_save(cr); 136 | gdk_cairo_set_source_rgba(cr, &grid_color); 137 | cairo_set_dash(cr, grid_pattern, grid_pattern_len, 0); 138 | cairo_move_to(cr, offset_left-0.5+GRAPH_SCALE_LINE_LENGTH/2.0, position); 139 | cairo_line_to(cr, offset_left-0.5+s.len+0.5, position); 140 | cairo_stroke(cr); 141 | cairo_restore(cr); 142 | } 143 | 144 | /* Draw a label */ 145 | gdk_cairo_set_source_rgba(cr, &color); 146 | g_snprintf(text, 147 | sizeof(text), 148 | (conf.signal_unit == UNIT_DBM) ? "%4d" : "%3d", 149 | current_value); 150 | cairo_move_to(cr, round(-1.5), round(position+GRAPH_FONT_SIZE/2.0-1.0)); 151 | cairo_show_text(cr, text); 152 | 153 | last_position = position; 154 | } 155 | current_value--; 156 | position += step; 157 | } 158 | 159 | curr = s.pos; 160 | for(i=draw_count; i >= 1; i--) 161 | { 162 | prev = ((curr==0) ? (s.len-1) : curr-1); 163 | next = ((curr==(s.len-1)) ? 0 : curr+1); 164 | if(!isnan(s.data[curr].value)) 165 | { 166 | if(s.data[curr].rds) 167 | gdk_cairo_set_source_rgba(cr, (conf.dark_theme ? &conf.color_rds_dark : &conf.color_rds)); 168 | else if(s.data[curr].stereo) 169 | gdk_cairo_set_source_rgba(cr, (conf.dark_theme ? &conf.color_stereo_dark : &conf.color_stereo)); 170 | else 171 | gdk_cairo_set_source_rgba(cr, (conf.dark_theme ? &conf.color_mono_dark : &conf.color_mono)); 172 | 173 | if(conf.signal_avg) 174 | { 175 | if(i == 1 || isnan(s.data[prev].value) || s.data[curr].freq != s.data[prev].freq) 176 | { 177 | if(isnan(s.data[next].value) || s.data[curr].freq != s.data[next].freq) 178 | value = s.data[curr].value; 179 | else 180 | value = (s.data[curr].value + s.data[next].value) / 2.0; 181 | } 182 | else if(i == draw_count || isnan(s.data[next].value) || s.data[curr].freq != s.data[next].freq) 183 | { 184 | if(isnan(s.data[prev].value) || s.data[curr].freq != s.data[prev].freq) 185 | value = s.data[curr].value; 186 | else 187 | value = (s.data[curr].value + s.data[prev].value) / 2.0; 188 | } 189 | else 190 | { 191 | value = (s.data[prev].value + s.data[curr].value + s.data[next].value) / 3.0; 192 | } 193 | } 194 | else 195 | { 196 | value = s.data[curr].value; 197 | } 198 | cairo_move_to(cr, offset_left+i, GRAPH_OFFSET_TOP+conf.signal_height); 199 | cairo_line_to(cr, offset_left+i, GRAPH_OFFSET_TOP+conf.signal_height-(signal_level(value) - min)*step); 200 | cairo_stroke(cr); 201 | } 202 | curr = prev; 203 | } 204 | 205 | /* Draw left vertical and bottom horizontal line */ 206 | gdk_cairo_set_source_rgba(cr, &graph_color_border); 207 | cairo_move_to(cr, offset_left-0.5, GRAPH_OFFSET_TOP+0.5); 208 | cairo_line_to(cr, offset_left-0.5, GRAPH_OFFSET_TOP+conf.signal_height+0.5); 209 | cairo_line_to(cr, offset_left-0.5+s.len+0.5, GRAPH_OFFSET_TOP+conf.signal_height+0.5); 210 | cairo_stroke(cr); 211 | 212 | return FALSE; 213 | } 214 | 215 | static gboolean 216 | graph_click(GtkWidget *widget, 217 | GdkEventButton *event, 218 | gpointer nothing) 219 | { 220 | if(event->type == GDK_BUTTON_PRESS && event->button == 3) /* right click */ 221 | signal_clear(); 222 | 223 | return FALSE; 224 | } 225 | 226 | void 227 | signal_push(gfloat value, 228 | gboolean stereo, 229 | gboolean rds, 230 | gboolean freq) 231 | { 232 | s.pos = (s.pos + 1)%s.len; 233 | s.data[s.pos].value = value; 234 | s.data[s.pos].stereo = stereo; 235 | s.data[s.pos].rds = rds; 236 | s.data[s.pos].freq = freq; 237 | } 238 | 239 | void 240 | signal_clear() 241 | { 242 | gint i; 243 | s.pos = 0; 244 | for(i=0; i 2 | #include 3 | #include 4 | #ifdef G_OS_WIN32 5 | #ifndef _WIN32_WINNT 6 | #define _WIN32_WINNT 0x0501 7 | #endif 8 | #include 9 | #include 10 | #include 11 | #include "win32.h" 12 | #else 13 | #include 14 | #include 15 | #include 16 | #endif 17 | #include "stationlist.h" 18 | #include "conf.h" 19 | #include "ui.h" 20 | #include "ui-tuner-set.h" 21 | #include "tuner-conn.h" 22 | #include "tuner.h" 23 | #include "version.h" 24 | 25 | #define STATIONLIST_BUFF 1024 26 | #define STATIONLIST_DEBUG 0 27 | #define STATIONLIST_AF_BUFF_LEN 25 28 | 29 | static gint stationlist_socket = -1; 30 | static gint stationlist_client = -1; 31 | static struct sockaddr_in stationlist_client_addr; 32 | static gint stationlist_sender; 33 | static GMutex stationlist_mutex; 34 | static GSList *stationlist_buffer; 35 | static guint8 stationlist_af_buffer[STATIONLIST_AF_BUFF_LEN]; 36 | static guint8 stationlist_af_buffer_pos; 37 | 38 | static gpointer stationlist_server(gpointer); 39 | static void stationlist_cmd(gchar*, gchar*); 40 | static gboolean stationlist_set_freq(gpointer); 41 | static gboolean stationlist_set_bw(gpointer); 42 | 43 | static void stationlist_add(gchar*, gchar*); 44 | static void stationlist_clear_rds(); 45 | static gboolean stationlist_send(gchar*); 46 | static void stationlist_free(gpointer); 47 | 48 | 49 | void 50 | stationlist_init() 51 | { 52 | stationlist_socket = socket(AF_INET, SOCK_DGRAM, 0); 53 | if(stationlist_socket < 0) 54 | { 55 | ui_dialog(ui.window, 56 | GTK_MESSAGE_ERROR, 57 | "SRCP", 58 | "stationlist_init: socket"); 59 | return; 60 | } 61 | 62 | #ifndef G_OS_WIN32 63 | gint on = 1; 64 | if(setsockopt(stationlist_socket, SOL_SOCKET, SO_REUSEADDR, (const char*) &on, sizeof(on)) < 0) 65 | { 66 | ui_dialog(ui.window, 67 | GTK_MESSAGE_ERROR, 68 | "SRCP", 69 | "stationlist_init: SO_REUSEADDR"); 70 | } 71 | #endif 72 | 73 | struct sockaddr_in addr; 74 | memset((char*)&addr, 0, sizeof(addr)); 75 | addr.sin_family = AF_INET; 76 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 77 | addr.sin_port = htons(conf.srcp_port); 78 | 79 | if(bind(stationlist_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) 80 | { 81 | ui_dialog(ui.window, 82 | GTK_MESSAGE_ERROR, 83 | "SRCP", 84 | "Failed to bind to a port: %d.\nIt may be already in use by another application.", 85 | conf.srcp_port); 86 | closesocket(stationlist_socket); 87 | stationlist_socket = -1; 88 | return; 89 | } 90 | 91 | stationlist_client = socket(AF_INET, SOCK_DGRAM, 0); 92 | if(stationlist_client < 0) 93 | { 94 | ui_dialog(ui.window, 95 | GTK_MESSAGE_ERROR, 96 | "SRCP", 97 | "stationlist_init: socket (client)"); 98 | return; 99 | } 100 | 101 | memset((char*)&stationlist_client_addr, 0, sizeof(stationlist_client_addr)); 102 | stationlist_client_addr.sin_family = AF_INET; 103 | stationlist_client_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 104 | stationlist_client_addr.sin_port = htons(conf.srcp_port-1); 105 | 106 | g_thread_unref(g_thread_new("thread_stationlist", stationlist_server, NULL)); 107 | g_mutex_init(&stationlist_mutex); 108 | stationlist_af_clear(); 109 | stationlist_sender = g_timeout_add(200, (GSourceFunc)stationlist_send, NULL); 110 | } 111 | 112 | gboolean 113 | stationlist_is_up() 114 | { 115 | return (stationlist_socket >= 0 && stationlist_client >= 0); 116 | } 117 | 118 | void 119 | stationlist_stop() 120 | { 121 | if(stationlist_is_up()) 122 | { 123 | shutdown(stationlist_client, 2); 124 | closesocket(stationlist_client); 125 | stationlist_client = -1; 126 | shutdown(stationlist_socket, 2); 127 | closesocket(stationlist_socket); 128 | stationlist_socket = -1; 129 | 130 | g_source_remove(stationlist_sender); 131 | g_mutex_lock(&stationlist_mutex); 132 | g_slist_free_full(stationlist_buffer, (GDestroyNotify)stationlist_free); 133 | stationlist_buffer = NULL; 134 | g_mutex_unlock(&stationlist_mutex); 135 | g_mutex_clear(&stationlist_mutex); 136 | } 137 | } 138 | 139 | static gpointer 140 | stationlist_server(gpointer nothing) 141 | { 142 | struct sockaddr_in addr; 143 | socklen_t len = sizeof(addr); 144 | gchar msg[STATIONLIST_BUFF], *ptr, *parse, *param, *value; 145 | gint n; 146 | 147 | while((n = recvfrom(stationlist_socket, msg, STATIONLIST_BUFF-1, 0, (struct sockaddr*)&addr, &len)) > 0) 148 | { 149 | msg[n] = 0; 150 | #if STATIONLIST_DEBUG 151 | printf("SL -> %s\n", msg); 152 | #endif 153 | parse = msg; 154 | while((ptr = strsep(&parse, ";"))) 155 | { 156 | param = strsep(&ptr, "="); 157 | value = strsep(&ptr, "="); 158 | if(param && value) 159 | { 160 | stationlist_cmd(param, value); 161 | } 162 | } 163 | stationlist_client_addr.sin_addr.s_addr = addr.sin_addr.s_addr; 164 | len = sizeof(addr); 165 | } 166 | return NULL; 167 | } 168 | 169 | static void 170 | stationlist_cmd(gchar *param, 171 | gchar *value) 172 | { 173 | if(!g_ascii_strcasecmp(param, "freq")) 174 | { 175 | if(!g_ascii_strcasecmp(value, "?")) 176 | stationlist_freq(tuner_get_freq()); 177 | else 178 | g_idle_add(stationlist_set_freq, GINT_TO_POINTER(atoi(value))); 179 | } 180 | else if(!g_ascii_strcasecmp(param, "bandwidth")) 181 | { 182 | if(!g_ascii_strcasecmp(value, "?")) 183 | stationlist_bw(tuner.bandwidth); 184 | else 185 | g_idle_add(stationlist_set_bw, GINT_TO_POINTER(atoi(value))); 186 | } 187 | } 188 | 189 | static gboolean 190 | stationlist_set_freq(gpointer freq) 191 | { 192 | tuner_set_frequency(GPOINTER_TO_INT(freq)/1000); 193 | return FALSE; 194 | } 195 | 196 | static gboolean 197 | stationlist_set_bw(gpointer bw) 198 | { 199 | gint index = tuner_filter_index_from_bw(GPOINTER_TO_INT(bw)); 200 | if(index >= 0) 201 | gtk_combo_box_set_active(GTK_COMBO_BOX(ui.c_bw), index); 202 | return FALSE; 203 | } 204 | 205 | void 206 | stationlist_freq(gint freq) 207 | { 208 | if(stationlist_is_up()) 209 | { 210 | stationlist_clear_rds(); 211 | stationlist_add(g_strdup("freq"), g_strdup_printf("%d", freq*1000)); 212 | } 213 | } 214 | 215 | void 216 | stationlist_rcvlevel(gint level) 217 | { 218 | if(stationlist_is_up()) 219 | { 220 | stationlist_add(g_strdup("RcvLevel"), g_strdup_printf("%d", level)); 221 | } 222 | } 223 | 224 | void 225 | stationlist_pi(gint pi) 226 | { 227 | if(stationlist_is_up()) 228 | { 229 | stationlist_add(g_strdup("pi"), g_strdup_printf("%04X", pi)); 230 | } 231 | } 232 | 233 | void 234 | stationlist_pty(gint pty) 235 | { 236 | if(stationlist_is_up()) 237 | { 238 | stationlist_add(g_strdup("pty"), g_strdup_printf("%01X", pty)); 239 | } 240 | } 241 | 242 | void 243 | stationlist_ecc(guchar ecc) 244 | { 245 | if(stationlist_is_up()) 246 | { 247 | stationlist_add(g_strdup("ecc"), g_strdup_printf("%02X", ecc)); 248 | } 249 | } 250 | 251 | void 252 | stationlist_ps(const gchar *ps) 253 | { 254 | if(stationlist_is_up()) 255 | { 256 | stationlist_add(g_strdup("ps"), g_strdup_printf("%2X%2X%2X%2X%2X%2X%2X%2X", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5], ps[6], ps[7])); 257 | } 258 | } 259 | 260 | void 261 | stationlist_rt(gint n, 262 | const gchar *rt) 263 | { 264 | GString *msg; 265 | gchar *msg_full; 266 | 267 | if(stationlist_is_up()) 268 | { 269 | msg = g_string_new(""); 270 | while(*rt) 271 | { 272 | g_string_append_printf(msg, "%02X", *rt++); 273 | } 274 | msg_full = g_string_free(msg, FALSE); 275 | stationlist_add(g_strdup_printf("rt%d", n+1), msg_full); 276 | } 277 | } 278 | 279 | void 280 | stationlist_bw(gint bw) 281 | { 282 | if(stationlist_is_up()) 283 | { 284 | stationlist_add(g_strdup("bandwidth"), g_strdup_printf("%d", bw)); 285 | } 286 | } 287 | 288 | void 289 | stationlist_af(gint af) 290 | { 291 | if(stationlist_is_up()) 292 | { 293 | stationlist_af_buffer[stationlist_af_buffer_pos] = af; 294 | stationlist_af_buffer_pos = (stationlist_af_buffer_pos + 1) % STATIONLIST_AF_BUFF_LEN; 295 | 296 | stationlist_add(g_strdup("af"), 297 | g_strdup_printf("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", 298 | stationlist_af_buffer[0], 299 | stationlist_af_buffer[1], 300 | stationlist_af_buffer[2], 301 | stationlist_af_buffer[3], 302 | stationlist_af_buffer[4], 303 | stationlist_af_buffer[5], 304 | stationlist_af_buffer[6], 305 | stationlist_af_buffer[7], 306 | stationlist_af_buffer[8], 307 | stationlist_af_buffer[9], 308 | stationlist_af_buffer[10], 309 | stationlist_af_buffer[11], 310 | stationlist_af_buffer[12], 311 | stationlist_af_buffer[13], 312 | stationlist_af_buffer[14], 313 | stationlist_af_buffer[15], 314 | stationlist_af_buffer[16], 315 | stationlist_af_buffer[17], 316 | stationlist_af_buffer[18], 317 | stationlist_af_buffer[19], 318 | stationlist_af_buffer[20], 319 | stationlist_af_buffer[21], 320 | stationlist_af_buffer[22], 321 | stationlist_af_buffer[23], 322 | stationlist_af_buffer[24]) 323 | ); 324 | } 325 | } 326 | 327 | void 328 | stationlist_af_clear() 329 | { 330 | gint i; 331 | for(i=0; idata; 351 | if(!strcmp(param, d->param)) 352 | { 353 | g_free(param); 354 | g_free(d->value); 355 | d->value = value; 356 | g_mutex_unlock(&stationlist_mutex); 357 | return; 358 | } 359 | } 360 | 361 | // or create new element 362 | d = g_new(sl_data_t, 1); 363 | d->param = param; 364 | d->value = value; 365 | stationlist_buffer = g_slist_append(stationlist_buffer, d); 366 | 367 | g_mutex_unlock(&stationlist_mutex); 368 | } 369 | 370 | static void 371 | stationlist_clear_rds() 372 | { 373 | GSList *l; 374 | sl_data_t *d; 375 | 376 | g_mutex_lock(&stationlist_mutex); 377 | 378 | l = stationlist_buffer; 379 | while(l) 380 | { 381 | d = l->data; 382 | l = g_slist_next(l); 383 | if(!strcmp("pi", d->param) || !strcmp("pty", d->param) || !strcmp("ecc", d->param) || !strcmp("ps", d->param) || !strcmp("rt", d->param) || !strcmp("af", d->param)) 384 | { 385 | stationlist_buffer = g_slist_remove(stationlist_buffer, d); 386 | stationlist_free(d); 387 | } 388 | } 389 | 390 | stationlist_af_clear(); 391 | g_mutex_unlock(&stationlist_mutex); 392 | } 393 | 394 | static gboolean 395 | stationlist_send(gchar *data) 396 | { 397 | GString *msg; 398 | GSList *l; 399 | gchar *msg_full; 400 | 401 | g_mutex_lock(&stationlist_mutex); 402 | if(!stationlist_buffer) 403 | { 404 | g_mutex_unlock(&stationlist_mutex); 405 | return TRUE; 406 | } 407 | 408 | msg = g_string_new(""); 409 | g_string_append_printf(msg, "from=%s", APP_NAME); 410 | for(l=stationlist_buffer; l; l=g_slist_next(l)) 411 | { 412 | g_string_append_printf(msg, ";%s=%s", ((sl_data_t*)l->data)->param, ((sl_data_t*)l->data)->value); 413 | } 414 | g_slist_free_full(stationlist_buffer, (GDestroyNotify)stationlist_free); 415 | stationlist_buffer = NULL; 416 | g_mutex_unlock(&stationlist_mutex); 417 | 418 | msg_full = g_string_free(msg, FALSE); 419 | #if STATIONLIST_DEBUG 420 | printf("SL <- %s\n", msg_full); 421 | #endif 422 | sendto(stationlist_client, msg_full, strlen(msg_full), 0, (struct sockaddr *)&stationlist_client_addr, sizeof(stationlist_client_addr)); 423 | g_free(msg_full); 424 | return TRUE; 425 | } 426 | 427 | static void 428 | stationlist_free(gpointer data) 429 | { 430 | sl_data_t *d = data; 431 | g_free(d->param); 432 | g_free(d->value); 433 | g_free(data); 434 | } 435 | -------------------------------------------------------------------------------- /src/ui-input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "ui.h" 7 | #include "scan.h" 8 | #include "ui-tuner-set.h" 9 | #include "rds-utils.h" 10 | #include "tuner.h" 11 | #include "conf.h" 12 | #include "log.h" 13 | #include "ui-input.h" 14 | 15 | gboolean rotation_shift_pressed; 16 | 17 | gboolean 18 | keyboard_press(GtkWidget *widget, 19 | GdkEventKey *event, 20 | gpointer disable_frequency_entry) 21 | { 22 | guint current = gdk_keyval_to_upper(event->keyval); 23 | gboolean shift_pressed = (event->state & GDK_SHIFT_MASK); 24 | gboolean ctrl_pressed = (event->state & GDK_CONTROL_MASK); 25 | gboolean alt_pressed = (event->state & GDK_MOD1_MASK); 26 | 27 | // tuning 28 | if(current == conf.key_tune_down) 29 | { 30 | tuner_modify_frequency(TUNER_FREQ_MODIFY_DOWN); 31 | return TRUE; 32 | } 33 | 34 | if(current == conf.key_tune_up) 35 | { 36 | tuner_modify_frequency(TUNER_FREQ_MODIFY_UP); 37 | return TRUE; 38 | } 39 | 40 | if(current == conf.key_tune_fine_up) 41 | { 42 | if(tuner_get_freq() < 1900) 43 | tuner_set_frequency(tuner_get_freq()+1); 44 | else 45 | tuner_set_frequency(tuner_get_freq()+(conf.fm_10k_steps || conf.tef668x_mode ? 10 : 5)); 46 | return TRUE; 47 | } 48 | 49 | if(current == conf.key_tune_fine_down) 50 | { 51 | if(tuner_get_freq() <= 1900) 52 | tuner_set_frequency(tuner_get_freq()-1); 53 | else 54 | tuner_set_frequency(tuner_get_freq()-(conf.fm_10k_steps || conf.tef668x_mode ? 10 : 5)); 55 | return TRUE; 56 | } 57 | 58 | if(current == conf.key_tune_jump_down) 59 | { 60 | tuner_set_frequency(tuner_get_freq()-1000); 61 | return TRUE; 62 | } 63 | 64 | if(current == conf.key_tune_jump_up) 65 | { 66 | tuner_set_frequency(tuner_get_freq()+1000); 67 | return TRUE; 68 | } 69 | 70 | if(current == conf.key_tune_back) 71 | { 72 | gtk_button_clicked(GTK_BUTTON(ui.b_tune_back)); 73 | return TRUE; 74 | } 75 | 76 | if(current == conf.key_tune_reset) 77 | { 78 | gtk_button_clicked(GTK_BUTTON(ui.b_tune_reset)); 79 | return TRUE; 80 | } 81 | 82 | if(current == conf.key_screenshot) 83 | { 84 | ui_screenshot(shift_pressed); 85 | return TRUE; 86 | } 87 | 88 | if(current == conf.key_rotate_cw) 89 | { 90 | rotation_shift_pressed = shift_pressed; 91 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_cw), TRUE); 92 | return TRUE; 93 | } 94 | 95 | if(current == conf.key_rotate_ccw) 96 | { 97 | rotation_shift_pressed = shift_pressed; 98 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_ccw), TRUE); 99 | return TRUE; 100 | } 101 | 102 | if(current == conf.key_switch_antenna) 103 | { 104 | gint current = gtk_combo_box_get_active(GTK_COMBO_BOX(ui.c_ant)); 105 | gtk_combo_box_set_active(GTK_COMBO_BOX(ui.c_ant), ((current < conf.ant_count-1) ? current+1 : 0)); 106 | return TRUE; 107 | } 108 | 109 | if(current == conf.key_rds_ps_mode) 110 | { 111 | ui_toggle_ps_mode(); 112 | return TRUE; 113 | } 114 | 115 | if(current == conf.key_scan_toggle) 116 | { 117 | scan_try_toggle(shift_pressed); 118 | return TRUE; 119 | } 120 | 121 | if(current == conf.key_scan_prev) 122 | { 123 | scan_try_prev(); 124 | return TRUE; 125 | } 126 | 127 | if(current == conf.key_scan_next) 128 | { 129 | scan_try_next(); 130 | return TRUE; 131 | } 132 | 133 | // presets 134 | if(event->keyval >= GDK_KEY_F1 && event->keyval <= GDK_KEY_F12) 135 | { 136 | gint id = event->keyval-GDK_KEY_F1; 137 | if(shift_pressed) 138 | { 139 | conf.presets[id] = tuner_get_freq(); 140 | ui_status(1500, "Preset F%d has been stored.", id+1); 141 | } 142 | else 143 | { 144 | tuner_set_frequency(conf.presets[id]); 145 | } 146 | return TRUE; 147 | } 148 | 149 | // decrease filter bandwidth 150 | if(current == conf.key_bw_down) 151 | { 152 | gint current = gtk_combo_box_get_active(GTK_COMBO_BOX(ui.c_bw)); 153 | gint offset = (gint)(tuner.mode == MODE_AM); /* don't set the 'default' option in AM mode */ 154 | if(current != gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtk_combo_box_get_model(GTK_COMBO_BOX(ui.c_bw))), NULL)-1-offset) 155 | { 156 | gtk_combo_box_set_active(GTK_COMBO_BOX(ui.c_bw), current+1); 157 | } 158 | return TRUE; 159 | } 160 | 161 | // increase filter bandwidth 162 | if(current == conf.key_bw_up) 163 | { 164 | gint current = gtk_combo_box_get_active(GTK_COMBO_BOX(ui.c_bw)); 165 | if(current != 0) 166 | { 167 | gtk_combo_box_set_active(GTK_COMBO_BOX(ui.c_bw), current-1); 168 | } 169 | return TRUE; 170 | } 171 | 172 | // adaptive filter bandwidth 173 | if(current == conf.key_bw_auto) 174 | { 175 | if(tuner.mode == MODE_FM) 176 | { 177 | gtk_combo_box_set_active(GTK_COMBO_BOX(ui.c_bw), 178 | gtk_tree_model_iter_n_children(GTK_TREE_MODEL(gtk_combo_box_get_model(GTK_COMBO_BOX(ui.c_bw))), NULL) - 1); 179 | } 180 | return TRUE; 181 | } 182 | 183 | if(current == conf.key_stereo_toggle) 184 | { 185 | tuner_set_forced_mono(!tuner.forced_mono); 186 | return TRUE; 187 | } 188 | 189 | if(current == conf.key_mode_toggle) 190 | { 191 | tuner_set_mode((tuner.mode != MODE_FM) ? MODE_FM : MODE_AM); 192 | return TRUE; 193 | } 194 | 195 | if(ctrl_pressed) 196 | { 197 | if(current == GDK_KEY_1) 198 | conf.title_tuner_mode = 0; 199 | else if(current == GDK_KEY_2) 200 | conf.title_tuner_mode = 1; 201 | else if(current == GDK_KEY_3) 202 | conf.title_tuner_mode = 2; 203 | else if(current == GDK_KEY_4) 204 | conf.title_tuner_mode = 3; 205 | else if(current == GDK_KEY_5) 206 | conf.title_tuner_mode = 4; 207 | else if(current == GDK_KEY_6) 208 | conf.title_tuner_mode = 5; 209 | 210 | g_source_remove(ui.title_timeout); 211 | ui_update_title(NULL); 212 | 213 | return TRUE; 214 | } 215 | 216 | if (alt_pressed) 217 | { 218 | if(current == GDK_KEY_1) 219 | { 220 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.x_rf), !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ui.x_rf))); 221 | tuner_set_gain(); 222 | } 223 | else if(current == GDK_KEY_2) 224 | { 225 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.x_if), !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ui.x_if))); 226 | tuner_set_gain(); 227 | } 228 | 229 | return TRUE; 230 | } 231 | 232 | if(disable_frequency_entry) 233 | { 234 | return FALSE; 235 | } 236 | 237 | // freq entry 238 | gtk_widget_grab_focus(ui.e_freq); 239 | gtk_editable_set_position(GTK_EDITABLE(ui.e_freq), -1); 240 | 241 | if (event->keyval == GDK_KEY_Return || 242 | event->keyval == GDK_KEY_KP_Enter) 243 | { 244 | gdouble freq = g_ascii_strtod(gtk_entry_get_text(GTK_ENTRY(ui.e_freq)), NULL); 245 | if (freq < 100000) 246 | tuner_set_frequency((gint)(freq * 1000)); 247 | 248 | gtk_entry_set_text(GTK_ENTRY(ui.e_freq), ""); 249 | return TRUE; 250 | } 251 | else if(event->state & GDK_CONTROL_MASK) 252 | { 253 | return FALSE; 254 | } 255 | else if((event->keyval < GDK_KEY_0 || event->keyval > GDK_KEY_9) && (event->keyval < GDK_KEY_KP_0 || event->keyval > GDK_KEY_KP_9) && event->keyval != GDK_KEY_BackSpace && event->keyval != '.') 256 | { 257 | return TRUE; 258 | } 259 | else 260 | { 261 | gchar buff[10], buff2[10]; 262 | gboolean flag = FALSE; 263 | gint i; 264 | 265 | g_snprintf(buff, 16, "%s", gtk_entry_get_text(GTK_ENTRY(ui.e_freq))); 266 | for(i=0; ikeyval == '.') 272 | return TRUE; 273 | } 274 | else if (!conf.extended_frequency) 275 | { 276 | i=atoi(buff); 277 | if(i>=20 && i<=200 && event->keyval != GDK_KEY_BackSpace && event->keyval != '.') 278 | { 279 | switch(event->keyval) 280 | { 281 | case GDK_KEY_KP_0: 282 | sprintf(buff2, ".0"); 283 | break; 284 | case GDK_KEY_KP_1: 285 | sprintf(buff2, ".1"); 286 | break; 287 | case GDK_KEY_KP_2: 288 | sprintf(buff2, ".2"); 289 | break; 290 | case GDK_KEY_KP_3: 291 | sprintf(buff2, ".3"); 292 | break; 293 | case GDK_KEY_KP_4: 294 | sprintf(buff2, ".4"); 295 | break; 296 | case GDK_KEY_KP_5: 297 | sprintf(buff2, ".5"); 298 | break; 299 | case GDK_KEY_KP_6: 300 | sprintf(buff2, ".6"); 301 | break; 302 | case GDK_KEY_KP_7: 303 | sprintf(buff2, ".7"); 304 | break; 305 | case GDK_KEY_KP_8: 306 | sprintf(buff2, ".8"); 307 | break; 308 | case GDK_KEY_KP_9: 309 | sprintf(buff2, ".9"); 310 | break; 311 | default: 312 | sprintf(buff2, ".%c", event->keyval); 313 | break; 314 | } 315 | 316 | gchar* content = gtk_editable_get_chars(GTK_EDITABLE(ui.e_freq), 0, -1); 317 | gint position = strlen(content); 318 | gtk_editable_insert_text(GTK_EDITABLE(ui.e_freq), buff2, sizeof(buff2), &position); 319 | gtk_editable_set_position(GTK_EDITABLE(ui.e_freq), -1); 320 | g_free(content); 321 | return TRUE; 322 | } 323 | } 324 | } 325 | return FALSE; 326 | } 327 | 328 | gboolean 329 | keyboard_release(GtkWidget *widget, 330 | GdkEventKey *event, 331 | gpointer nothing) 332 | { 333 | guint current = gdk_keyval_to_upper(event->keyval); 334 | rotation_shift_pressed |= (event->state & GDK_SHIFT_MASK); 335 | 336 | if(current == conf.key_rotate_cw) 337 | { 338 | if(!rotation_shift_pressed) 339 | { 340 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_cw), FALSE); 341 | return TRUE; 342 | } 343 | } 344 | else if(current == conf.key_rotate_ccw) 345 | { 346 | if(!rotation_shift_pressed) 347 | { 348 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_ccw), FALSE); 349 | return TRUE; 350 | } 351 | } 352 | 353 | return FALSE; 354 | } 355 | 356 | gboolean 357 | mouse_window(GtkWidget *widget, 358 | GdkEventButton *event, 359 | GtkWindow *window) 360 | { 361 | static gboolean compact_mode = FALSE; 362 | 363 | if(event->type == GDK_BUTTON_PRESS && event->button == 1 && event->state & GDK_SHIFT_MASK) 364 | { 365 | gtk_window_begin_move_drag(window, event->button, event->x_root, event->y_root, event->time); 366 | return TRUE; 367 | } 368 | else if(event->type == GDK_3BUTTON_PRESS && event->button == 1) 369 | { 370 | if(compact_mode) 371 | { 372 | gtk_widget_hide(ui.box_left_settings1); 373 | gtk_widget_hide(ui.box_left_settings2); 374 | } 375 | else 376 | { 377 | gtk_widget_show(ui.box_left_settings1); 378 | gtk_widget_show(ui.box_left_settings2); 379 | } 380 | compact_mode = !compact_mode; 381 | } 382 | return FALSE; 383 | } 384 | 385 | gboolean 386 | mouse_freq(GtkWidget *widget, 387 | GdkEvent *event, 388 | gpointer nothing) 389 | { 390 | const gchar *freq_text = gtk_label_get_text(GTK_LABEL(ui.l_freq)); 391 | const gchar *pi_text = gtk_label_get_text(GTK_LABEL(ui.l_pi)); 392 | gchar buff[30]; 393 | gchar *ps; 394 | 395 | if (rdsparser_string_get_available(rdsparser_get_ps(tuner.rds))) 396 | { 397 | ps = rds_utils_text(rdsparser_get_ps(tuner.rds)); 398 | if(conf.replace_spaces) 399 | { 400 | char *replaced = replace_spaces(ps); 401 | g_snprintf(buff, sizeof(buff), "%s %s %s", freq_text, pi_text, replaced); 402 | g_free(replaced); 403 | } 404 | else 405 | { 406 | g_snprintf(buff, sizeof(buff), "%s %s %s", freq_text, pi_text, ps); 407 | } 408 | g_free(ps); 409 | } 410 | else if(tuner.rds_pi >= 0) 411 | { 412 | g_snprintf(buff, sizeof(buff), "%s %s", freq_text, pi_text); 413 | } 414 | else 415 | { 416 | g_snprintf(buff, sizeof(buff), "%s", freq_text); 417 | } 418 | 419 | gtk_clipboard_set_text(gtk_widget_get_clipboard(ui.window, GDK_SELECTION_CLIPBOARD), 420 | buff, 421 | -1); 422 | return TRUE; 423 | } 424 | 425 | gboolean 426 | mouse_scroll(GtkWidget *widget, 427 | GdkEventScroll *event, 428 | gpointer user_data) 429 | { 430 | if (!conf.signal_scroll) 431 | return TRUE; 432 | 433 | if (event->direction == GDK_SCROLL_DOWN) 434 | tuner_modify_frequency(TUNER_FREQ_MODIFY_DOWN); 435 | else if (event->direction == GDK_SCROLL_UP) 436 | tuner_modify_frequency(TUNER_FREQ_MODIFY_UP); 437 | 438 | return TRUE; 439 | } 440 | 441 | gboolean 442 | mouse_pi(GtkWidget *widget, 443 | GdkEvent *event, 444 | gpointer nothing) 445 | { 446 | if(tuner.rds_pi < 0) 447 | return FALSE; 448 | 449 | gtk_clipboard_set_text(gtk_widget_get_clipboard(ui.window, GDK_SELECTION_CLIPBOARD), 450 | gtk_label_get_text(GTK_LABEL(ui.l_pi)), 451 | -1); 452 | return TRUE; 453 | } 454 | 455 | gboolean 456 | mouse_ps(GtkWidget *widget, 457 | GdkEventButton *event, 458 | gpointer data) 459 | { 460 | if (event->type == GDK_BUTTON_PRESS && 461 | event->button == 3) // right click 462 | { 463 | ui_toggle_ps_mode(); 464 | return FALSE; 465 | } 466 | 467 | if (!rdsparser_string_get_available(rdsparser_get_ps(tuner.rds))) 468 | { 469 | return FALSE; 470 | } 471 | 472 | gchar *ps = rds_utils_text(rdsparser_get_ps(tuner.rds)); 473 | if (conf.replace_spaces) 474 | { 475 | gchar *str = replace_spaces(ps); 476 | gtk_clipboard_set_text(gtk_widget_get_clipboard(ui.window, GDK_SELECTION_CLIPBOARD), str, -1); 477 | g_free(str); 478 | } 479 | else 480 | { 481 | gtk_clipboard_set_text(gtk_widget_get_clipboard(ui.window, GDK_SELECTION_CLIPBOARD), ps, -1); 482 | } 483 | 484 | g_free(ps); 485 | return TRUE; 486 | } 487 | 488 | gboolean 489 | mouse_rt(GtkWidget *widget, 490 | GdkEventButton *event, 491 | gpointer data) 492 | { 493 | rdsparser_rt_flag_t flag = (rdsparser_rt_flag_t)GPOINTER_TO_INT(data); 494 | 495 | if (event->type == GDK_BUTTON_PRESS && 496 | event->button == 3) // right click 497 | { 498 | ui_toggle_rt_mode(); 499 | return FALSE; 500 | } 501 | 502 | gchar *rt = rds_utils_text(rdsparser_get_rt(tuner.rds, flag)); 503 | 504 | if (conf.replace_spaces) 505 | { 506 | gchar *str = replace_spaces(rt); 507 | gtk_clipboard_set_text(gtk_widget_get_clipboard(ui.window, GDK_SELECTION_CLIPBOARD), 508 | str, 509 | -1); 510 | g_free(str); 511 | } 512 | else 513 | { 514 | gtk_clipboard_set_text(gtk_widget_get_clipboard(ui.window, GDK_SELECTION_CLIPBOARD), 515 | rt, 516 | -1); 517 | } 518 | 519 | g_free(rt); 520 | return FALSE; 521 | } 522 | -------------------------------------------------------------------------------- /src/ui-connect.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #ifdef G_OS_WIN32 6 | #include 7 | #include "win32.h" 8 | #endif 9 | #include "ui-connect.h" 10 | #include "ui.h" 11 | #include "conf.h" 12 | #include "tuner.h" 13 | #include "version.h" 14 | #include "tuner-conn.h" 15 | #include "tuner.h" 16 | #include "ui-tuner-set.h" 17 | #include "ui-signal.h" 18 | 19 | static GtkWidget *dialog, *content; 20 | static GtkWidget *r_serial, *c_serial; 21 | static GtkWidget *r_tcp; 22 | static GtkWidget *box_tcp1, *l_host, *e_host, *l_port, *e_port; 23 | static GtkWidget *box_tcp2, *l_password, *e_password, *c_password; 24 | static GtkWidget *box_status_wrapper, *box_status, *spinner, *l_status; 25 | static GtkWidget *box_button, *b_connect, *b_cancel; 26 | static GtkListStore *ls_host; 27 | 28 | static conn_t *connecting; 29 | static gboolean wait_for_tuner; 30 | static guint dialog_callback = 0; 31 | static gboolean successfully_connected = FALSE; 32 | 33 | static void connection_dialog_destroy(GtkWidget*, gpointer); 34 | static void connection_dialog_select(GtkWidget*, gpointer); 35 | static void connection_dialog_connect(GtkWidget*, gpointer); 36 | static void connection_dialog_status(const gchar*); 37 | static void connection_dialog_unlock(gboolean); 38 | static gboolean connection_dialog_key(GtkWidget*, GdkEventKey*, gpointer); 39 | static void connection_serial_state(gint); 40 | static void connection_dialog_connected(gint, gint); 41 | static gboolean connection_dialog_callback(gpointer); 42 | 43 | void 44 | connection_toggle() 45 | { 46 | if(tuner.thread) 47 | { 48 | connect_button(TRUE); 49 | if(!conf.disconnect_confirm || ui_dialog_confirm_disconnect()) 50 | { 51 | /* The tuner is connected, shutdown it */ 52 | tuner_write(tuner.thread, "X"); 53 | /* Lock the connection button until the thread ends */ 54 | gtk_widget_set_sensitive(ui.b_connect, FALSE); 55 | g_usleep(100000); 56 | tuner_thread_cancel(tuner.thread); 57 | } 58 | return; 59 | } 60 | 61 | /* Drop current rotator state */ 62 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_cw), FALSE); 63 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui.b_ccw), FALSE); 64 | 65 | /* Display connection dialog */ 66 | connection_dialog(FALSE); 67 | } 68 | 69 | void 70 | connection_dialog(gboolean auto_connect) 71 | { 72 | gint i; 73 | connecting = NULL; 74 | wait_for_tuner = FALSE; 75 | 76 | dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); 77 | gtk_window_set_title(GTK_WINDOW(dialog), "Connect"); 78 | gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(ui.window)); 79 | gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); 80 | gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE); 81 | gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); 82 | gtk_container_set_border_width(GTK_CONTAINER(dialog), 5); 83 | g_signal_connect(dialog, "destroy", G_CALLBACK(connection_dialog_destroy), NULL); 84 | 85 | content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 3); 86 | gtk_container_add(GTK_CONTAINER(dialog), content); 87 | 88 | r_serial = gtk_radio_button_new_with_label(NULL, "Serial port"); 89 | gtk_box_pack_start(GTK_BOX(content), r_serial, TRUE, TRUE, 2); 90 | c_serial = gtk_combo_box_text_new(); 91 | #ifdef G_OS_WIN32 92 | gchar tmp[10]; 93 | for(i=1; i<=99; i++) 94 | { 95 | g_snprintf(tmp, sizeof(tmp), "COM%d", i); 96 | gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(c_serial), tmp); 97 | if(g_ascii_strcasecmp(tmp, conf.serial) == 0) 98 | { 99 | gtk_combo_box_set_active(GTK_COMBO_BOX(c_serial), i-1); 100 | } 101 | } 102 | #else 103 | struct dirent *dir; 104 | DIR *d = opendir("/dev"); 105 | i=0; 106 | if(d) 107 | { 108 | while((dir = readdir(d))) 109 | { 110 | #ifdef __APPLE__ 111 | if(!strncmp(dir->d_name, "cu.usbserial", 12)) 112 | #else 113 | if(!strncmp(dir->d_name, "ttyUSB", 6) || !strncmp(dir->d_name, "ttyACM", 6) || !strncmp(dir->d_name, "ttyS", 4) || !strncmp(dir->d_name, "rfcomm", 6)) 114 | #endif 115 | { 116 | gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(c_serial), dir->d_name); 117 | if(g_ascii_strcasecmp(dir->d_name, conf.serial) == 0) 118 | { 119 | gtk_combo_box_set_active(GTK_COMBO_BOX(c_serial), i); 120 | } 121 | i++; 122 | } 123 | } 124 | closedir(d); 125 | } 126 | #endif 127 | g_signal_connect(c_serial, "changed", G_CALLBACK(connection_dialog_select), r_serial); 128 | gtk_box_pack_start(GTK_BOX(content), c_serial, TRUE, TRUE, 0); 129 | 130 | r_tcp = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(r_serial), "TCP/IP"); 131 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(r_tcp), conf.network); 132 | gtk_box_pack_start(GTK_BOX(content), r_tcp, TRUE, TRUE, 2); 133 | box_tcp1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); 134 | gtk_container_add(GTK_CONTAINER(content), box_tcp1); 135 | l_host = gtk_label_new("Host:"); 136 | gtk_box_pack_start(GTK_BOX(box_tcp1), l_host, TRUE, FALSE, 1); 137 | ls_host = gtk_list_store_new(1, G_TYPE_STRING); 138 | if(conf.host) 139 | { 140 | for(i=0; conf.host[i]; i++) 141 | { 142 | GtkTreeIter iter; 143 | gtk_list_store_append(ls_host, &iter); 144 | gtk_list_store_set(ls_host, &iter, 0, conf.host[i], -1); 145 | } 146 | } 147 | e_host = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(ls_host)); 148 | gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(e_host), 0); 149 | gtk_combo_box_set_active(GTK_COMBO_BOX(e_host), 0); 150 | g_signal_connect(e_host, "changed", G_CALLBACK(connection_dialog_select), r_tcp); 151 | gtk_box_pack_start(GTK_BOX(box_tcp1), e_host, TRUE, FALSE, 1); 152 | l_port = gtk_label_new("Port:"); 153 | gtk_box_pack_start(GTK_BOX(box_tcp1), l_port, TRUE, FALSE, 1); 154 | e_port = gtk_entry_new(); 155 | gtk_entry_set_max_length(GTK_ENTRY(e_port), 5); 156 | gtk_entry_set_width_chars(GTK_ENTRY(e_port), 5); 157 | gchar *s_port = g_strdup_printf("%d", conf.port); 158 | gtk_entry_set_text(GTK_ENTRY(e_port), s_port); 159 | g_free(s_port); 160 | g_signal_connect(e_port, "changed", G_CALLBACK(connection_dialog_select), r_tcp); 161 | gtk_box_pack_start(GTK_BOX(box_tcp1), e_port, TRUE, FALSE, 1); 162 | 163 | box_tcp2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); 164 | gtk_container_add(GTK_CONTAINER(content), box_tcp2); 165 | l_password = gtk_label_new("Password:"); 166 | gtk_box_pack_start(GTK_BOX(box_tcp2), l_password, FALSE, FALSE, 1); 167 | e_password = gtk_entry_new(); 168 | gtk_entry_set_width_chars(GTK_ENTRY(e_password), 20); 169 | gtk_entry_set_text(GTK_ENTRY(e_password), conf.password); 170 | gtk_entry_set_visibility(GTK_ENTRY(e_password), FALSE); 171 | g_signal_connect(e_password, "changed", G_CALLBACK(connection_dialog_select), r_tcp); 172 | gtk_box_pack_start(GTK_BOX(box_tcp2), e_password, TRUE, TRUE, 1); 173 | c_password = gtk_check_button_new_with_label("Keep"); 174 | if(conf.password && strlen(conf.password)) 175 | { 176 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c_password), TRUE); 177 | } 178 | g_signal_connect(c_password, "toggled", G_CALLBACK(connection_dialog_select), r_tcp); 179 | gtk_box_pack_start(GTK_BOX(box_tcp2), c_password, FALSE, FALSE, 1); 180 | 181 | box_status_wrapper = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); 182 | gtk_box_pack_start(GTK_BOX(content), box_status_wrapper, FALSE, FALSE, 1); 183 | 184 | box_status = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); 185 | gtk_widget_set_hexpand(box_status, TRUE); 186 | gtk_box_pack_start(GTK_BOX(box_status_wrapper), box_status, FALSE, FALSE, 1); 187 | 188 | spinner = gtk_spinner_new(); 189 | gtk_box_pack_start(GTK_BOX(box_status), spinner, FALSE, FALSE, 1); 190 | 191 | l_status = gtk_label_new(NULL); 192 | gtk_box_pack_start(GTK_BOX(box_status), l_status, FALSE, FALSE, 1); 193 | 194 | box_button = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL); 195 | gtk_button_box_set_layout(GTK_BUTTON_BOX(box_button), GTK_BUTTONBOX_END); 196 | gtk_box_set_spacing(GTK_BOX(box_button), 5); 197 | gtk_box_pack_start(GTK_BOX(content), box_button, FALSE, FALSE, 5); 198 | 199 | b_connect = gtk_button_new_with_label("OK"); 200 | gtk_button_set_image(GTK_BUTTON(b_connect), gtk_image_new_from_icon_name("gtk-ok", GTK_ICON_SIZE_BUTTON)); 201 | g_signal_connect(dialog, "key-press-event", G_CALLBACK(connection_dialog_key), b_connect); 202 | g_signal_connect(b_connect, "clicked", G_CALLBACK(connection_dialog_connect), NULL); 203 | gtk_container_add(GTK_CONTAINER(box_button), b_connect); 204 | 205 | b_cancel = gtk_button_new_with_label("Cancel"); 206 | gtk_button_set_image(GTK_BUTTON(b_cancel), gtk_image_new_from_icon_name("gtk-cancel", GTK_ICON_SIZE_BUTTON)); 207 | g_signal_connect_swapped(b_cancel, "clicked", G_CALLBACK(gtk_widget_destroy), dialog); 208 | gtk_container_add(GTK_CONTAINER(box_button), b_cancel); 209 | 210 | #ifdef G_OS_WIN32 211 | if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ui.b_ontop))) 212 | { 213 | gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE); 214 | } 215 | #endif 216 | 217 | gtk_widget_show_all(dialog); 218 | gtk_widget_hide(spinner); 219 | gtk_widget_hide(l_status); 220 | connect_button(FALSE); 221 | if(auto_connect || (conf.auto_reconnect && successfully_connected)) 222 | { 223 | gtk_button_clicked(GTK_BUTTON(b_connect)); 224 | } 225 | 226 | #ifdef G_OS_WIN32 227 | if (conf.dark_theme) 228 | win32_dark_titlebar(dialog); 229 | #endif 230 | } 231 | 232 | static void 233 | connection_dialog_destroy(GtkWidget *widget, 234 | gpointer data) 235 | { 236 | if(connecting) 237 | { 238 | /* Waiting for socket connection, force its thread to end */ 239 | connecting->canceled = TRUE; 240 | connecting = NULL; 241 | } 242 | 243 | if(wait_for_tuner) 244 | { 245 | /* Waiting for tuner, force its thread to end */ 246 | tuner_thread_cancel(tuner.thread); 247 | wait_for_tuner = FALSE; 248 | } 249 | 250 | if(dialog_callback) 251 | { 252 | g_source_remove(dialog_callback); 253 | dialog_callback = 0; 254 | } 255 | } 256 | 257 | static void 258 | connection_dialog_select(GtkWidget *widget, 259 | gpointer data) 260 | { 261 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data), TRUE); 262 | } 263 | 264 | static void 265 | connection_dialog_connect(GtkWidget *widget, 266 | gpointer data) 267 | { 268 | const gchar *hostname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(e_host)))); 269 | const gchar *port = gtk_entry_get_text(GTK_ENTRY(e_port)); 270 | const gchar *password = gtk_entry_get_text(GTK_ENTRY(e_password)); 271 | gint result; 272 | gintptr fd; 273 | 274 | if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(r_serial))) 275 | { 276 | /* Serial port */ 277 | gchar *serial = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(c_serial)); 278 | if(serial) 279 | { 280 | connection_dialog_unlock(FALSE); 281 | g_snprintf(ui.window_title, 100, "%s / %s", APP_NAME, serial); 282 | conf_update_string_const(&conf.serial, serial); 283 | conf.network = FALSE; 284 | 285 | result = tuner_open_serial(serial, &fd); 286 | if(result == CONN_SUCCESS) 287 | connection_dialog_connected(TUNER_THREAD_SERIAL, fd); 288 | else 289 | connection_serial_state(result); 290 | 291 | g_free(serial); 292 | } 293 | } 294 | else if(strlen(hostname) && atoi(port) > 0) 295 | { 296 | /* Network */ 297 | connection_dialog_unlock(FALSE); 298 | connecting = conn_new(hostname, port, password); 299 | g_snprintf(ui.window_title, 100, "%s / %s", APP_NAME, hostname); 300 | 301 | conf_add_host(hostname); 302 | conf.port = atoi(port); 303 | conf.network = TRUE; 304 | if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c_password))) 305 | { 306 | conf_update_string_const(&conf.password, password); 307 | } 308 | 309 | g_thread_unref(g_thread_new("open_socket", tuner_open_socket, connecting)); 310 | } 311 | } 312 | 313 | static void 314 | connection_dialog_status(const gchar* string) 315 | { 316 | gchar *markup; 317 | gtk_widget_show(l_status); 318 | markup = g_markup_printf_escaped("%s", string); 319 | gtk_label_set_markup(GTK_LABEL(l_status), markup); 320 | g_free(markup); 321 | } 322 | 323 | static void 324 | connection_dialog_unlock(gboolean value) 325 | { 326 | gtk_widget_set_sensitive(r_serial, value); 327 | gtk_widget_set_sensitive(c_serial, value); 328 | gtk_widget_set_sensitive(r_tcp, value); 329 | gtk_widget_set_sensitive(e_host, value); 330 | gtk_widget_set_sensitive(e_port, value); 331 | gtk_widget_set_sensitive(e_password, value); 332 | gtk_widget_set_sensitive(c_password, value); 333 | gtk_widget_set_sensitive(b_connect, value); 334 | if(!value) 335 | { 336 | gtk_widget_show(spinner); 337 | gtk_spinner_start(GTK_SPINNER(spinner)); 338 | } 339 | else 340 | { 341 | gtk_widget_hide(spinner); 342 | } 343 | } 344 | 345 | static gboolean 346 | connection_dialog_key(GtkWidget *widget, 347 | GdkEventKey *event, 348 | gpointer button) 349 | { 350 | guint current = gdk_keyval_to_upper(event->keyval); 351 | if(current == GDK_KEY_Escape) 352 | { 353 | gtk_widget_destroy(widget); 354 | return TRUE; 355 | } 356 | else if(current == GDK_KEY_Return) 357 | { 358 | if (gtk_widget_get_sensitive(GTK_WIDGET(button))) 359 | gtk_button_clicked(GTK_BUTTON(button)); 360 | return TRUE; 361 | } 362 | return FALSE; 363 | } 364 | 365 | static void 366 | connection_serial_state(gint result) 367 | { 368 | switch(result) 369 | { 370 | case CONN_SERIAL_FAIL_OPEN: 371 | connection_dialog_status("Unable to open the serial port."); 372 | break; 373 | 374 | case CONN_SERIAL_FAIL_PARM_R: 375 | connection_dialog_status("Unable to read serial port parameters."); 376 | break; 377 | 378 | case CONN_SERIAL_FAIL_PARM_W: 379 | connection_dialog_status("Unable to set serial port parameters."); 380 | break; 381 | 382 | case CONN_SERIAL_FAIL_SPEED: 383 | connection_dialog_status("Unable to set serial port speed."); 384 | break; 385 | } 386 | successfully_connected = FALSE; 387 | connection_dialog_unlock(TRUE); 388 | } 389 | 390 | static void 391 | connection_dialog_connected(gint mode, 392 | gint fd) 393 | { 394 | gtk_window_set_title(GTK_WINDOW(ui.window), ui.window_title); 395 | signal_clear(); 396 | connection_dialog_status("Waiting for tuner..."); 397 | gtk_widget_set_sensitive(ui.b_connect, FALSE); 398 | 399 | wait_for_tuner = TRUE; 400 | tuner.thread = tuner_thread_new(mode, fd); 401 | 402 | dialog_callback = g_timeout_add(100, connection_dialog_callback, NULL); 403 | } 404 | 405 | static gboolean 406 | connection_dialog_callback(gpointer userdata) 407 | { 408 | if (!tuner.ready && tuner.thread) 409 | return G_SOURCE_CONTINUE; 410 | 411 | if (!tuner.thread) 412 | { 413 | if (wait_for_tuner) 414 | { 415 | connection_dialog_unlock(TRUE); 416 | connection_dialog_status("Connection has been unexpectedly closed."); 417 | wait_for_tuner = FALSE; 418 | } 419 | successfully_connected = FALSE; 420 | dialog_callback = 0; 421 | return G_SOURCE_REMOVE; 422 | } 423 | 424 | successfully_connected = TRUE; 425 | 426 | if (tuner.send_settings) 427 | { 428 | tuner_set_volume(); 429 | tuner_set_squelch(); 430 | tuner_set_mode(tuner.mode); 431 | tuner_set_antenna(); 432 | tuner_set_frequency(conf.initial_freq); 433 | tuner_set_agc(); 434 | tuner_set_bandwidth(); 435 | tuner_set_deemphasis(); 436 | tuner_set_gain(); 437 | } 438 | 439 | connect_button(TRUE); 440 | gtk_widget_set_sensitive(ui.b_connect, TRUE); 441 | wait_for_tuner = FALSE; 442 | gtk_widget_destroy(dialog); 443 | 444 | dialog_callback = 0; 445 | return G_SOURCE_REMOVE; 446 | } 447 | 448 | gboolean 449 | connection_socket_callback(gpointer ptr) 450 | { 451 | /* Gets final state of conn_t ptr and frees up the memory */ 452 | conn_t *data = (conn_t*)ptr; 453 | if(data->canceled && data->state == CONN_SUCCESS) 454 | { 455 | closesocket(data->socketfd); 456 | } 457 | else if(!data->canceled) 458 | { 459 | connecting = NULL; 460 | switch(data->state) 461 | { 462 | case CONN_SUCCESS: 463 | connection_dialog_connected(TUNER_THREAD_SOCKET, data->socketfd); 464 | break; 465 | 466 | case CONN_SOCKET_FAIL_RESOLV: 467 | connection_dialog_status("Unable to resolve the hostname."); 468 | connection_dialog_unlock(TRUE); 469 | break; 470 | 471 | case CONN_SOCKET_FAIL_CONN: 472 | connection_dialog_status("Unable to connect to a server."); 473 | connection_dialog_unlock(TRUE); 474 | break; 475 | 476 | case CONN_SOCKET_FAIL_AUTH: 477 | connection_dialog_status("Authentication error."); 478 | connection_dialog_unlock(TRUE); 479 | break; 480 | 481 | case CONN_SOCKET_FAIL_WRITE: 482 | connection_dialog_status("Socket write error."); 483 | connection_dialog_unlock(TRUE); 484 | break; 485 | } 486 | } 487 | 488 | conn_free(data); 489 | return FALSE; 490 | } 491 | 492 | gboolean 493 | connection_socket_callback_info(gpointer ptr) 494 | { 495 | /* Like connection_socket_callback but does not free up the conn_t pointer */ 496 | conn_t *data = (conn_t*)ptr; 497 | if(!data->canceled) 498 | { 499 | switch(data->state) 500 | { 501 | case CONN_SOCKET_STATE_RESOLV: 502 | connection_dialog_status("Resolving hostname..."); 503 | break; 504 | 505 | case CONN_SOCKET_STATE_CONN: 506 | connection_dialog_status("Connecting..."); 507 | break; 508 | 509 | case CONN_SOCKET_STATE_AUTH: 510 | connection_dialog_status("Authenticating..."); 511 | break; 512 | } 513 | } 514 | return FALSE; 515 | } 516 | 517 | void 518 | connection_socket_auth_fail() 519 | { 520 | if(wait_for_tuner) 521 | { 522 | connection_dialog_status("Incorrect password."); 523 | connection_dialog_unlock(TRUE); 524 | wait_for_tuner = FALSE; 525 | } 526 | } 527 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /src/tuner.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifdef G_OS_WIN32 8 | #ifndef _WIN32_WINNT 9 | #define _WIN32_WINNT 0x0501 10 | #endif 11 | #include 12 | #include 13 | #include 14 | #else 15 | #include 16 | #include 17 | #endif 18 | 19 | #include "tuner.h" 20 | #include "log.h" 21 | #include "stationlist.h" 22 | #include "tuner-callbacks.h" 23 | #include "ui-tuner-update.h" 24 | #include "conf.h" 25 | #include "rds-utils.h" 26 | 27 | #define DEBUG_READ 1 28 | #define DEBUG_WRITE 1 29 | 30 | #define SERIAL_BUFFER 10000 31 | tuner_t tuner; 32 | 33 | #define CALLBACK_PRIORITY G_PRIORITY_HIGH_IDLE 34 | 35 | typedef struct tuner_thread 36 | { 37 | gintptr fd; 38 | gint type; /* TUNER_THREAD_SERIAL or TUNER_THREAD_SOCKET */ 39 | volatile gboolean canceled; 40 | } tuner_thread_t; 41 | 42 | static gpointer tuner_thread(gpointer); 43 | static gboolean tuner_parse(gchar, gchar*); 44 | static void tuner_restart(gintptr); 45 | static gboolean tuner_write_serial(gintptr, gchar*, int); 46 | 47 | gpointer 48 | tuner_thread_new(gint type, 49 | gintptr fd) 50 | { 51 | tuner_thread_t *thread = g_malloc(sizeof(tuner_thread_t)); 52 | g_assert(type == TUNER_THREAD_SERIAL || 53 | type == TUNER_THREAD_SOCKET); 54 | 55 | thread->fd = fd; 56 | thread->type = type; 57 | thread->canceled = FALSE; 58 | 59 | g_thread_unref(g_thread_new("tuner", tuner_thread, (gpointer)thread)); 60 | return thread; 61 | } 62 | 63 | void 64 | tuner_thread_cancel(gpointer thread) 65 | { 66 | ((tuner_thread_t*)thread)->canceled = TRUE; 67 | } 68 | 69 | static gpointer 70 | tuner_thread(gpointer data) 71 | { 72 | tuner_thread_t *thread = (tuner_thread_t*)data; 73 | gchar buffer[SERIAL_BUFFER]; 74 | gint pos = 0; 75 | 76 | struct timeval timeout; 77 | fd_set input; 78 | gint n; 79 | 80 | g_print("thread start: %p\n", data); 81 | 82 | #ifdef G_OS_WIN32 83 | DWORD len_in = 0; 84 | BOOL fWaitingOnRead = FALSE; 85 | DWORD state; 86 | OVERLAPPED osReader = {0}; 87 | if(thread->type == TUNER_THREAD_SERIAL) 88 | { 89 | osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 90 | if(osReader.hEvent == NULL) 91 | goto tuner_thread_cleanup; 92 | } 93 | #endif 94 | 95 | /* Arduino may restart during port opening */ 96 | if(thread->type == TUNER_THREAD_SERIAL) 97 | g_usleep(1750 * 1000); 98 | 99 | if(thread->canceled) 100 | goto tuner_thread_cleanup; 101 | 102 | tuner_write(thread, "x"); 103 | 104 | while(!thread->canceled) 105 | { 106 | #ifdef G_OS_WIN32 107 | if(thread->type == TUNER_THREAD_SOCKET) 108 | { 109 | FD_ZERO(&input); 110 | FD_SET(thread->fd, &input); 111 | timeout.tv_sec = 0; 112 | timeout.tv_usec = 50000; 113 | n = select(thread->fd+1, &input, NULL, NULL, &timeout); 114 | if(!n) 115 | continue; 116 | if(n < 0 || recv(thread->fd, &buffer[pos], 1, 0) <= 0) 117 | break; 118 | } 119 | else 120 | { 121 | if (!fWaitingOnRead) 122 | { 123 | if (!ReadFile((HANDLE)thread->fd, &buffer[pos], 1, &len_in, &osReader)) 124 | { 125 | if (GetLastError() != ERROR_IO_PENDING) 126 | { 127 | CloseHandle(osReader.hEvent); 128 | break; 129 | } 130 | else 131 | { 132 | fWaitingOnRead = TRUE; 133 | } 134 | } 135 | } 136 | 137 | if (fWaitingOnRead) 138 | { 139 | state = WaitForSingleObject(osReader.hEvent, 200); 140 | if(state == WAIT_TIMEOUT) 141 | { 142 | continue; 143 | } 144 | if(state != WAIT_OBJECT_0) 145 | { 146 | CloseHandle(osReader.hEvent); 147 | break; 148 | } 149 | 150 | if (!GetOverlappedResult((HANDLE)thread->fd, &osReader, &len_in, FALSE)) 151 | { 152 | CloseHandle(osReader.hEvent); 153 | break; 154 | } 155 | 156 | fWaitingOnRead = FALSE; 157 | } 158 | if(len_in != 1) 159 | { 160 | continue; 161 | } 162 | } 163 | #else 164 | FD_ZERO(&input); 165 | FD_SET(thread->fd, &input); 166 | timeout.tv_sec = 0; 167 | timeout.tv_usec = 50000; 168 | n = select(thread->fd+1, &input, NULL, NULL, &timeout); 169 | if(!n) 170 | continue; 171 | if(n < 0 || read(thread->fd, &buffer[pos], 1) <= 0) 172 | break; 173 | #endif 174 | /* If this command is too long to 175 | * fit into a buffer, clip it */ 176 | if(buffer[pos] != '\n') 177 | { 178 | if(pos != SERIAL_BUFFER-1) 179 | pos++; 180 | continue; 181 | } 182 | buffer[pos] = 0; 183 | pos = 0; 184 | 185 | #if DEBUG_READ 186 | g_print("read: %s\n", buffer); 187 | #endif 188 | if(!tuner_parse(buffer[0], buffer+1)) 189 | break; 190 | } 191 | 192 | tuner_thread_cleanup: 193 | if(thread->type == TUNER_THREAD_SOCKET) 194 | { 195 | #ifdef G_OS_WIN32 196 | shutdown(thread->fd, 2); 197 | closesocket(thread->fd); 198 | #else 199 | shutdown(thread->fd, 2); 200 | close(thread->fd); 201 | #endif 202 | } 203 | else 204 | { 205 | tuner_restart(thread->fd); 206 | #ifdef G_OS_WIN32 207 | CloseHandle((HANDLE)thread->fd); 208 | #else 209 | close(thread->fd); 210 | #endif 211 | } 212 | 213 | g_idle_add_full(CALLBACK_PRIORITY, tuner_disconnect, thread, NULL); 214 | g_print("thread stop: %p\n", data); 215 | return NULL; 216 | } 217 | 218 | static gboolean 219 | tuner_parse(gchar c, 220 | gchar *msg) 221 | { 222 | if(c == 'O' && msg[0] == 'K') 223 | { 224 | /* Tuner startup */ 225 | g_idle_add_full(CALLBACK_PRIORITY, tuner_ready, NULL, NULL); 226 | } 227 | else if(c == 'X') 228 | { 229 | /* Tuner shutdown */ 230 | return FALSE; 231 | } 232 | else if(c == 'T') 233 | { 234 | /* Tuned frequency */ 235 | g_idle_add_full(CALLBACK_PRIORITY, tuner_freq, GINT_TO_POINTER(atoi(msg)), NULL); 236 | } 237 | else if(c == 'V') 238 | { 239 | /* DAA tuning voltage */ 240 | g_idle_add_full(CALLBACK_PRIORITY, tuner_daa, GINT_TO_POINTER(atoi(msg)), NULL); 241 | } 242 | else if(c == 'S' && strlen(msg) >= 2) 243 | { 244 | /* Signal strength and quality indicators */ 245 | tuner_signal_t *data = g_malloc(sizeof(tuner_signal_t)); 246 | gchar *ptr; 247 | switch(msg[0]) 248 | { 249 | case 's': 250 | data->stereo = SIGNAL_STEREO; 251 | break; 252 | case 'S': 253 | data->stereo = SIGNAL_STEREO | SIGNAL_FORCED_MONO; 254 | break; 255 | case 'M': 256 | data->stereo = SIGNAL_FORCED_MONO; 257 | break; 258 | default: 259 | data->stereo = SIGNAL_MONO; 260 | break; 261 | } 262 | data->value = g_ascii_strtod(msg+1, NULL); 263 | g_idle_add_full(CALLBACK_PRIORITY, tuner_signal, data, NULL); 264 | 265 | if((ptr = strchr(msg, ','))) 266 | { 267 | g_idle_add_full(CALLBACK_PRIORITY, tuner_cci, GINT_TO_POINTER(atoi(ptr+1)), NULL); 268 | if((ptr = strchr(ptr+1, ','))) 269 | g_idle_add_full(CALLBACK_PRIORITY, tuner_aci, GINT_TO_POINTER(atoi(ptr+1)), NULL); 270 | } 271 | } 272 | else if(c == 'P' && strlen(msg) >= 4) 273 | { 274 | /* PI code */ 275 | guint pi = strtoul(msg, NULL, 16); 276 | guint8 err = 0; 277 | gchar *ptr; 278 | 279 | for(ptr = msg+4; *ptr; ptr++) 280 | if(*ptr == '?') 281 | err++; 282 | pi |= (((err > 3) ? 3 : err) << 16); 283 | 284 | g_idle_add_full(CALLBACK_PRIORITY, tuner_pi, GUINT_TO_POINTER(pi), NULL); 285 | } 286 | else if(c == 'R' && strlen(msg) == 14) 287 | { 288 | /* RDS data */ 289 | g_idle_add_full(CALLBACK_PRIORITY, tuner_rds_legacy, g_strdup(msg), NULL); 290 | } 291 | else if(c == 'R' && strlen(msg) == 18) 292 | { 293 | /* RDS data */ 294 | g_idle_add_full(CALLBACK_PRIORITY, tuner_rds_new, g_strdup(msg), NULL); 295 | } 296 | else if(c == 'U') 297 | { 298 | /* Spectral scan */ 299 | tuner_scan_t *scan = tuner_scan_parse(msg); 300 | if(scan) 301 | g_idle_add_full(CALLBACK_PRIORITY, tuner_scan, (gpointer)scan, NULL); 302 | } 303 | else if(c == 'N') 304 | { 305 | /* Stereo pilot injection level estimation */ 306 | g_idle_add_full(CALLBACK_PRIORITY, tuner_pilot, GINT_TO_POINTER(atoi(msg)), NULL); 307 | } 308 | else if(c == 'Y') 309 | { 310 | /* Sound volume control */ 311 | g_idle_add_full(CALLBACK_PRIORITY, tuner_volume, GINT_TO_POINTER(atoi(msg)), NULL); 312 | } 313 | else if(c == 'A') 314 | { 315 | /* RF AGC threshold */ 316 | g_idle_add_full(CALLBACK_PRIORITY, tuner_agc, GINT_TO_POINTER(atoi(msg)), NULL); 317 | } 318 | else if(c == 'D') 319 | { 320 | /* De-emphasis */ 321 | g_idle_add_full(CALLBACK_PRIORITY, tuner_deemphasis, GINT_TO_POINTER(atoi(msg)), NULL); 322 | } 323 | else if(c == 'Z') 324 | { 325 | /* Antenna switch */ 326 | g_idle_add_full(CALLBACK_PRIORITY, tuner_antenna, GINT_TO_POINTER(atoi(msg)), NULL); 327 | } 328 | else if(c == 'G') 329 | { 330 | /* RF & IF gain setting */ 331 | g_idle_add_full(CALLBACK_PRIORITY, tuner_gain, GINT_TO_POINTER(atoi(msg)), NULL); 332 | } 333 | else if(c == 'M') 334 | { 335 | /* FM / AM mode */ 336 | g_idle_add_full(CALLBACK_PRIORITY, tuner_mode, GINT_TO_POINTER(atoi(msg)), NULL); 337 | } 338 | else if(c == 'F') 339 | { 340 | /* Filter */ 341 | g_idle_add_full(CALLBACK_PRIORITY, tuner_filter, GINT_TO_POINTER(atoi(msg)), NULL); 342 | } 343 | else if(c == 'W') 344 | { 345 | /* Bandwidth */ 346 | g_idle_add_full(CALLBACK_PRIORITY, tuner_bandwidth, GINT_TO_POINTER(atoi(msg)), NULL); 347 | } 348 | else if(c == 'Q') 349 | { 350 | /* Squelch */ 351 | g_idle_add_full(CALLBACK_PRIORITY, tuner_squelch, GINT_TO_POINTER(atoi(msg)), NULL); 352 | } 353 | else if(c == 'C') 354 | { 355 | /* Rotator control */ 356 | g_idle_add_full(CALLBACK_PRIORITY, tuner_rotator, GINT_TO_POINTER(atoi(msg)), NULL); 357 | } 358 | else if(c == 'I') 359 | { 360 | /* Custom signal level sampling interval */ 361 | g_idle_add_full(CALLBACK_PRIORITY, tuner_sampling_interval, GINT_TO_POINTER(atoi(msg)), NULL); 362 | } 363 | else if(c == '!') 364 | { 365 | /* External event */ 366 | g_idle_add_full(CALLBACK_PRIORITY, tuner_event, NULL, NULL); 367 | } 368 | else if(c == 'o') 369 | { 370 | /* Online users (network) */ 371 | gchar *ptr; 372 | g_idle_add_full(CALLBACK_PRIORITY, tuner_online, GINT_TO_POINTER(atoi(msg)), NULL); 373 | if((ptr = strchr(msg, ','))) 374 | g_idle_add_full(CALLBACK_PRIORITY, tuner_online_guests, GINT_TO_POINTER(atoi(ptr+1)), NULL); 375 | } 376 | else if(c == 'a') 377 | { 378 | /* Authorization (network) */ 379 | gint auth = atoi(msg); 380 | if(!auth) 381 | { 382 | g_idle_add_full(CALLBACK_PRIORITY, tuner_unauthorized, NULL, NULL); 383 | return FALSE; 384 | } 385 | else if(auth == 1) 386 | { 387 | g_idle_add_full(CALLBACK_PRIORITY, tuner_ready, GINT_TO_POINTER(TRUE), NULL); 388 | } 389 | } 390 | return TRUE; 391 | } 392 | 393 | static void 394 | tuner_restart(gintptr fd) 395 | { 396 | #ifdef G_OS_WIN32 397 | EscapeCommFunction((HANDLE)fd, CLRDTR); 398 | EscapeCommFunction((HANDLE)fd, CLRRTS); 399 | g_usleep(10000); 400 | EscapeCommFunction((HANDLE)fd, SETDTR); 401 | EscapeCommFunction((HANDLE)fd, SETRTS); 402 | #else 403 | gint n; 404 | if(ioctl(fd, TIOCMGET, &n) == -1) 405 | return; 406 | n &= ~(TIOCM_DTR | TIOCM_RTS); 407 | ioctl(fd, TIOCMSET, &n); 408 | g_usleep(10000); 409 | n |= (TIOCM_DTR | TIOCM_RTS); 410 | ioctl(fd, TIOCMSET, &n); 411 | #endif 412 | } 413 | 414 | void 415 | tuner_write(gpointer ptr, 416 | gchar *command) 417 | { 418 | tuner_thread_t *thread; 419 | gchar *msg; 420 | gint len; 421 | gboolean ret; 422 | 423 | if(!ptr) 424 | return; 425 | 426 | thread = (tuner_thread_t*)ptr; 427 | msg = g_strdup(command); 428 | len = strlen(command); 429 | msg[len++] = '\n'; 430 | 431 | if(thread->type == TUNER_THREAD_SERIAL) 432 | ret = tuner_write_serial(thread->fd, msg, len); 433 | else 434 | ret = tuner_write_socket(thread->fd, msg, len); 435 | 436 | g_free(msg); 437 | #if DEBUG_WRITE 438 | g_print("write%s: %s\n", 439 | (!ret ? " ERROR" : ""), 440 | command); 441 | #endif 442 | } 443 | 444 | #ifdef G_OS_WIN32 445 | static gboolean 446 | tuner_write_serial(gintptr fd, 447 | gchar *msg, 448 | gint len) 449 | { 450 | OVERLAPPED osWrite = {0}; 451 | DWORD dwWritten; 452 | osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 453 | if(osWrite.hEvent == NULL) 454 | return FALSE; 455 | if (!WriteFile((HANDLE)fd, msg, len, &dwWritten, &osWrite)) 456 | if (GetLastError() == ERROR_IO_PENDING) 457 | if(WaitForSingleObject(osWrite.hEvent, INFINITE) == WAIT_OBJECT_0) 458 | GetOverlappedResult((HANDLE)fd, &osWrite, &dwWritten, FALSE); 459 | CloseHandle(osWrite.hEvent); 460 | return TRUE; 461 | } 462 | #else 463 | static gboolean 464 | tuner_write_serial(gintptr fd, 465 | gchar *msg, 466 | gint len) 467 | { 468 | gint sent = 0; 469 | gint n; 470 | do 471 | { 472 | n = write(fd, msg+sent, len-sent); 473 | if(n < 0) 474 | return FALSE; 475 | sent += n; 476 | } 477 | while(sent < len); 478 | return TRUE; 479 | } 480 | #endif 481 | 482 | gboolean 483 | tuner_write_socket(gintptr fd, 484 | gchar *msg, 485 | gint len) 486 | { 487 | gint sent = 0; 488 | gint n; 489 | 490 | do 491 | { 492 | #ifdef G_OS_WIN32 493 | n = send(fd, msg+sent, len-sent, 0); 494 | #else 495 | n = send(fd, msg+sent, len-sent, MSG_NOSIGNAL); 496 | #endif 497 | if(n < 0) 498 | { 499 | shutdown(fd, 2); 500 | return FALSE; 501 | } 502 | sent += n; 503 | } 504 | while(sent < len); 505 | return TRUE; 506 | } 507 | 508 | static void 509 | callback_pty(rdsparser_t *rds, 510 | void *user_data) 511 | { 512 | ui_update_pty(); 513 | } 514 | 515 | static void 516 | callback_tp(rdsparser_t *rds, 517 | void *user_data) 518 | { 519 | ui_update_tp(); 520 | } 521 | 522 | static void 523 | callback_ta(rdsparser_t *rds, 524 | void *user_data) 525 | { 526 | ui_update_ta(); 527 | } 528 | 529 | static void 530 | callback_ms(rdsparser_t *rds, 531 | void *user_data) 532 | { 533 | ui_update_ms(); 534 | } 535 | 536 | static void 537 | callback_country(rdsparser_t *rds, 538 | void *user_data) 539 | { 540 | ui_update_country(); 541 | } 542 | 543 | static void 544 | callback_af(rdsparser_t *rds, 545 | uint32_t new_af, 546 | void *user_data) 547 | { 548 | ui_update_af(new_af); 549 | } 550 | 551 | static void 552 | callback_ps(rdsparser_t *rds, 553 | void *user_data) 554 | { 555 | ui_update_ps(); 556 | 557 | const rdsparser_string_t *ps = rdsparser_get_ps(rds); 558 | gchar *ps_text = rds_utils_text(ps); 559 | stationlist_ps(ps_text); 560 | 561 | gboolean error = FALSE; 562 | const rdsparser_string_error_t *errors = rdsparser_string_get_errors(ps); 563 | const uint8_t length = rdsparser_string_get_length(ps); 564 | for (gint i = 0; i < length; i++) 565 | { 566 | if (errors[i]) 567 | { 568 | error = TRUE; 569 | break; 570 | } 571 | } 572 | 573 | log_ps(ps_text, error); 574 | g_free(ps_text); 575 | } 576 | 577 | static void 578 | callback_rt(rdsparser_t *rds, 579 | rdsparser_rt_flag_t flag, 580 | void *user_data) 581 | { 582 | ui_update_rt(flag); 583 | const rdsparser_string_t *rt = rdsparser_get_rt(rds, flag); 584 | gchar *rt_text = rds_utils_text(rt); 585 | stationlist_rt(flag, rt_text); 586 | log_rt(flag, rt_text); 587 | g_free(rt_text); 588 | } 589 | 590 | static void 591 | callback_ct(rdsparser_t *rds, 592 | const rdsparser_ct_t *ct, 593 | void *user_data) 594 | { 595 | int16_t offset = rdsparser_ct_get_offset(ct); 596 | char *datetime = g_strdup_printf("%04d-%02d-%02d %02d:%02d %c%02d:%02d", 597 | rdsparser_ct_get_year(ct), 598 | rdsparser_ct_get_month(ct), 599 | rdsparser_ct_get_day(ct), 600 | rdsparser_ct_get_hour(ct), 601 | rdsparser_ct_get_minute(ct), 602 | (offset >= 0) ? '+' : '-', 603 | abs(offset / 60), 604 | abs(offset % 60)); 605 | 606 | log_ct(datetime); 607 | g_free(datetime); 608 | 609 | } 610 | 611 | void 612 | tuner_rds_init() 613 | { 614 | tuner.rds = rdsparser_new(); 615 | 616 | tuner_rds_configure(); 617 | 618 | rdsparser_register_pty(tuner.rds, callback_pty); 619 | rdsparser_register_tp(tuner.rds, callback_tp); 620 | rdsparser_register_ta(tuner.rds, callback_ta); 621 | rdsparser_register_ms(tuner.rds, callback_ms); 622 | rdsparser_register_country(tuner.rds, callback_country); 623 | rdsparser_register_af(tuner.rds, callback_af); 624 | rdsparser_register_ps(tuner.rds, callback_ps); 625 | rdsparser_register_rt(tuner.rds, callback_rt); 626 | rdsparser_register_ct(tuner.rds, callback_ct); 627 | } 628 | 629 | void 630 | tuner_rds_configure() 631 | { 632 | rdsparser_set_extended_check(tuner.rds, conf.rds_extended_check); 633 | 634 | rdsparser_block_error_t ps_info_error = (conf.rds_ps_progressive && conf.rds_ps_prog_override ? RDSPARSER_BLOCK_ERROR_LARGE : conf.rds_ps_info_error); 635 | rdsparser_block_error_t ps_data_error = (conf.rds_ps_progressive && conf.rds_ps_prog_override ? RDSPARSER_BLOCK_ERROR_LARGE : conf.rds_ps_data_error); 636 | rdsparser_set_text_correction(tuner.rds, RDSPARSER_TEXT_PS, RDSPARSER_BLOCK_TYPE_INFO, ps_info_error); 637 | rdsparser_set_text_correction(tuner.rds, RDSPARSER_TEXT_PS, RDSPARSER_BLOCK_TYPE_DATA, ps_data_error); 638 | rdsparser_set_text_progressive(tuner.rds, RDSPARSER_TEXT_PS, (conf.rds_ps_progressive ? RDSPARSER_PROGRESSIVE_AUTO : RDSPARSER_PROGRESSIVE_DISABLED)); 639 | 640 | rdsparser_block_error_t rt_info_error = (conf.rds_rt_progressive && conf.rds_rt_prog_override ? RDSPARSER_BLOCK_ERROR_LARGE : conf.rds_rt_info_error); 641 | rdsparser_block_error_t rt_data_error = (conf.rds_rt_progressive && conf.rds_rt_prog_override ? RDSPARSER_BLOCK_ERROR_LARGE : conf.rds_rt_data_error); 642 | rdsparser_set_text_correction(tuner.rds, RDSPARSER_TEXT_RT, RDSPARSER_BLOCK_TYPE_INFO, rt_info_error); 643 | rdsparser_set_text_correction(tuner.rds, RDSPARSER_TEXT_RT, RDSPARSER_BLOCK_TYPE_DATA, rt_data_error); 644 | rdsparser_set_text_progressive(tuner.rds, RDSPARSER_TEXT_RT, (conf.rds_rt_progressive ? RDSPARSER_PROGRESSIVE_AUTO : RDSPARSER_PROGRESSIVE_DISABLED)); 645 | } 646 | 647 | void tuner_clear_all() 648 | { 649 | log_cleanup(); 650 | 651 | tuner.freq = 0; 652 | tuner.prevfreq = 0; 653 | tuner.prevantenna = 0; 654 | ui_update_freq(); 655 | 656 | tuner.sampling_interval = 0; 657 | tuner.signal = NAN; 658 | tuner.forced_mono = FALSE; 659 | tuner_clear_signal(); 660 | ui_update_signal(); 661 | 662 | tuner.cci = -1; 663 | ui_update_cci(); 664 | tuner.aci = -1; 665 | ui_update_aci(); 666 | 667 | tuner_clear_rds(); 668 | 669 | tuner.ready = FALSE; 670 | tuner.ready_tuned = FALSE; 671 | tuner.guest = FALSE; 672 | tuner.online = 0; 673 | tuner.online_guests = 0; 674 | tuner.send_settings = TRUE; 675 | tuner.thread = NULL; 676 | 677 | tuner.rotator = 0; 678 | ui_update_rotator(); 679 | 680 | ui_update_disconnected(); 681 | } 682 | 683 | void tuner_clear_signal() 684 | { 685 | tuner.signal_max = NAN; 686 | tuner.signal_sum = 0.0; 687 | tuner.signal_samples = 0; 688 | 689 | tuner.stereo = FALSE; 690 | ui_update_stereo_flag(); 691 | } 692 | 693 | void tuner_clear_rds() 694 | { 695 | tuner.rds_timeout = 0; 696 | ui_update_rds_flag(); 697 | tuner.rds_reset_timer = 0; 698 | 699 | tuner.rds_pi = -1; 700 | tuner.rds_pi_err_level = G_MAXINT; 701 | ui_update_pi(); 702 | 703 | rdsparser_clear(tuner.rds); 704 | 705 | ui_update_tp(); 706 | ui_update_ta(); 707 | ui_update_ms(); 708 | ui_update_pty(); 709 | ui_update_country(); 710 | ui_update_ps(); 711 | ui_update_rt(0); 712 | ui_update_rt(1); 713 | 714 | ui_clear_af(); 715 | } 716 | 717 | gint tuner_get_freq() 718 | { 719 | return tuner.freq - tuner.offset[tuner.antenna]; 720 | } 721 | 722 | gint tuner_get_offset() 723 | { 724 | return tuner.offset[tuner.antenna]; 725 | } 726 | 727 | void tuner_set_offset(gint antenna, 728 | gint offset) 729 | { 730 | if(antenna >= 0 && antenna <= ANT_COUNT) 731 | tuner.offset[antenna] = offset; 732 | } 733 | --------------------------------------------------------------------------------