/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2002 by Stuart Martin * RTC config saving code (C) 2002 by hessu@hes.iki.fi * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include #include #include #include "inttypes.h" #include "config.h" #include "rbpaths.h" #include "action.h" #include "crc32.h" #include "sound.h" #include "settings.h" #include "debug.h" #include "usb.h" #include "backlight.h" #include "audio.h" #include "talk.h" #include "string-extra.h" #include "rtc.h" #include "power.h" #include "ata_idle_notify.h" #include "storage.h" #include "screens.h" #include "ctype.h" #include "file.h" #include "system.h" #include "general.h" #include "misc.h" #include "icons.h" #include "font.h" #include "peakmeter.h" #include "lang.h" #include "language.h" #include "powermgmt.h" #include "keyboard.h" #include "version.h" #include "rbunicode.h" #include "dircache.h" #include "splash.h" #include "list.h" #include "settings_list.h" #include "filetypes.h" #include "option_select.h" #if CONFIG_TUNER #include "radio.h" #endif #include "wps.h" #include "skin_engine/skin_engine.h" #include "viewport.h" #include "statusbar-skinned.h" #include "bootchart.h" #include "scroll_engine.h" struct user_settings global_settings; struct system_status global_status; #include "dsp_proc_settings.h" #include "playback.h" #ifdef HAVE_RECORDING #include "enc_config.h" #endif #include "pcm_sampr.h" #define NVRAM_DATA_START 8 #define NVRAM_BLOCK_SIZE (sizeof(struct system_status) + NVRAM_DATA_START) #define MAX_LINES 10 #ifdef HAVE_REMOTE_LCD #include "lcd-remote.h" #endif #if defined(DX50) || defined(DX90) #include "governor-ibasso.h" #include "usb-ibasso.h" #endif #ifdef ROCKBOX_NO_TEMP_SETTINGS_FILE /* Overwrites same file each time */ #define CONFIGFILE_TEMP CONFIGFILE #define NVRAM_FILE_TEMP NVRAM_FILE #define rename_temp_file(a,b,c) #else /* creates temp files on save, renames next load, saves old file if desired */ #define CONFIGFILE_TEMP CONFIGFILE".new" #define NVRAM_FILE_TEMP NVRAM_FILE".new" static void rename_temp_file(const char *tempfile, const char *file, const char *oldfile) { /* if tempfile does not exist -- Return * if oldfile is supplied -- Rename file to oldfile * if tempfile does exist -- Rename tempfile to file */ if (file_exists(tempfile)) { if (oldfile != NULL && file_exists(file)) rename(file, oldfile); rename(tempfile, file); } } #endif long lasttime = 0; /** NVRAM stuff, if the target doesnt have NVRAM it is saved in ROCKBOX_DIR /nvram.bin **/ /* NVRAM is set out as [0] 'R' [1] 'b' [2] version [3] stored variable count [4-7] crc32 checksum [8-NVRAM_BLOCK_SIZE] data */ static char nvram_buffer[NVRAM_BLOCK_SIZE]; static bool read_nvram_data(char* buf, int max_len) { rename_temp_file(NVRAM_FILE_TEMP, NVRAM_FILE, NVRAM_FILE".old"); unsigned crc32 = 0xffffffff; int var_count = 0, i = 0, buf_pos = 0; int fd = open(NVRAM_FILE, O_RDONLY); int bytes; if (fd < 0) return false; memset(buf,0,max_len); bytes = read(fd,buf,max_len); close(fd); if (bytes < 8) /* min is 8 bytes,magic, ver, vars, crc32 */ return false; /* check magic, version */ if ((buf[0] != 'R') || (buf[1] != 'b') || (buf[2] != NVRAM_CONFIG_VERSION)) return false; /* check crc32 */ crc32 = crc_32(&buf[NVRAM_DATA_START], max_len-NVRAM_DATA_START-1,0xffffffff); if (memcmp(&crc32,&buf[4],4)) return false; /* all good, so read in the settings */ var_count = buf[3]; buf_pos = NVRAM_DATA_START; for(i=0; i>F_NVRAM_MASK_SHIFT; if (nvram_bytes) { if ((var_count>0) && (buf_pos>F_NVRAM_MASK_SHIFT; if (nvram_bytes) { memcpy(&buf[buf_pos],settings[i].setting,nvram_bytes); buf_pos += nvram_bytes; var_count++; } } /* count and crc32 */ buf[3] = var_count; crc32 = crc_32(&buf[NVRAM_DATA_START], max_len-NVRAM_DATA_START-1,0xffffffff); memcpy(&buf[4],&crc32,4); fd = open(NVRAM_FILE_TEMP,O_CREAT|O_TRUNC|O_WRONLY, 0666); if (fd >= 0) { int len = write(fd,buf,max_len); close(fd); if (len < 8) return false; } return true; } /** Reading from a config file **/ /* * load settings from disk or RTC RAM */ void settings_load(int which) { if (which&SETTINGS_RTC) read_nvram_data(nvram_buffer,NVRAM_BLOCK_SIZE); if (which&SETTINGS_HD) { rename_temp_file(CONFIGFILE_TEMP, CONFIGFILE, CONFIGFILE".old"); settings_load_config(CONFIGFILE, false); settings_load_config(FIXEDSETTINGSFILE, false); } } bool cfg_string_to_int(int setting_id, int* out, const char* str) { const char* start = settings[setting_id].cfg_vals; char* end = NULL; char temp[MAX_PATH]; int count = 0; while (1) { end = strchr(start, ','); if (!end) { if (!strcmp(str, start)) { *out = count; return true; } else return false; } strmemccpy(temp, start, end-start+1); if (!strcmp(str, temp)) { *out = count; return true; } start = end +1; count++; } return false; } /** * Copy an input string to an output buffer, stripping the prefix and * suffix listed in the filename setting. Returns false if the output * string does not fit in the buffer or is longer than the setting's * max_len, and the output buffer will not be modified. * * Returns true if the setting was copied successfully. The input and * output buffers are allowed to alias. */ bool copy_filename_setting(char *buf, size_t buflen, const char *input, const struct filename_setting *fs) { size_t input_len = strlen(input); size_t len; if (fs->prefix) { len = strlen(fs->prefix); if (len <= input_len && !strncasecmp(input, fs->prefix, len)) { input += len; input_len -= len; } } if (fs->suffix) { len = strlen(fs->suffix); if (len <= input_len && !strcasecmp(input + input_len - len, fs->suffix)) { input_len -= len; } } /* Make sure it fits the output buffer and repsects the setting's max_len. * Note that max_len is a buffer size and thus includes a null terminator */ if (input_len >= (size_t)fs->max_len || input_len >= buflen) return false; /* Copy what remains into buf - use memmove in case of aliasing */ memmove(buf, input, input_len); buf[input_len] = '\0'; return true; } bool settings_load_config(const char* file, bool apply) { const struct settings_list *setting; int index; int fd; char line[128]; char* name; char* value; bool theme_changed = false; fd = open_utf8(file, O_RDONLY); if (fd < 0) return false; while (read_line(fd, line, sizeof line) > 0) { if (!settings_parseline(line, &name, &value)) continue; setting = find_setting_by_cfgname(name, &index); if (!setting) continue; if (setting->flags & F_THEMESETTING) theme_changed = true; switch (setting->flags & F_T_MASK) { case F_T_CUSTOM: setting->custom_setting->load_from_cfg(setting->setting, value); break; case F_T_INT: case F_T_UINT: #ifdef HAVE_LCD_COLOR if (setting->flags & F_RGB) hex_to_rgb(value, (int*)setting->setting); else #endif if (setting->cfg_vals == NULL) { *(int*)setting->setting = atoi(value); } else { int temp, *v = (int*)setting->setting; bool found = cfg_string_to_int(index, &temp, value); if (found) { if (setting->flags & F_TABLE_SETTING) *v = setting->table_setting->values[temp]; else *v = temp; } else if (setting->flags & F_ALLOW_ARBITRARY_VALS) { *v = atoi(value); } } break; case F_T_BOOL: { int temp; if (cfg_string_to_int(index, &temp, value)) *(bool*)setting->setting = !!temp; if (setting->bool_setting->option_callback) setting->bool_setting->option_callback(!!temp); break; } /* these can be plain text, filenames, or dirnames */ case F_T_CHARPTR: case F_T_UCHARPTR: { const struct filename_setting *fs = setting->filename_setting; copy_filename_setting((char*)setting->setting, fs->max_len, value, fs); break; } } } /* while(...) */ close(fd); if (apply) { settings_save(); settings_apply(true); if (theme_changed) settings_apply_skins(); } return true; } /** Writing to a config file and saving settings **/ bool cfg_int_to_string(int setting_id, int val, char* buf, int buf_len) { int flags = settings[setting_id].flags; const char* start = settings[setting_id].cfg_vals; char* end = NULL; int count = 0; if ((flags&F_T_MASK)==F_T_INT && flags&F_TABLE_SETTING) { const int *value = settings[setting_id].table_setting->values; while (start) { end = strchr(start,','); if (value[count] == val) { if (end == NULL) strmemccpy(buf, start, buf_len); else { int len = MIN(buf_len, (end-start) + 1); strmemccpy(buf, start, len); } return true; } count++; if (end) start = end+1; else break; } return false; } while (count < val) { start = strchr(start,','); if (!start) return false; count++; start++; } end = strchr(start,','); if (end == NULL) strmemccpy(buf, start, buf_len); else { int len = MIN(buf_len, (end-start) + 1); strmemccpy(buf, start, len); } return true; } bool cfg_to_string(int i/*setting_id*/, char* buf, int buf_len) { switch (settings[i].flags&F_T_MASK) { case F_T_CUSTOM: settings[i].custom_setting->write_to_cfg(settings[i].setting, buf, buf_len); break; case F_T_INT: case F_T_UINT: #ifdef HAVE_LCD_COLOR if (settings[i].flags&F_RGB) { int colour = *(int*)settings[i].setting; snprintf(buf,buf_len,"%02x%02x%02x", (int)RGB_UNPACK_RED(colour), (int)RGB_UNPACK_GREEN(colour), (int)RGB_UNPACK_BLUE(colour)); } else #endif if (settings[i].cfg_vals == NULL) { snprintf(buf,buf_len,"%d",*(int*)settings[i].setting); } else { if (cfg_int_to_string(i, *(int*)settings[i].setting, buf, buf_len) == false) { snprintf(buf,buf_len,"%d",*(int*)settings[i].setting); } else return false; } break; case F_T_BOOL: cfg_int_to_string(i, *(bool*)settings[i].setting==false?0:1, buf, buf_len); break; case F_T_CHARPTR: case F_T_UCHARPTR: if (((char*)settings[i].setting)[0] && settings[i].filename_setting->prefix) { if (((char*)settings[i].setting)[0] == '-') { buf[0] = '-'; buf[1] = '\0'; } else { snprintf(buf,buf_len,"%s%s%s", settings[i].filename_setting->prefix, (char*)settings[i].setting, settings[i].filename_setting->suffix); } } else { int len = MIN(buf_len, settings[i].filename_setting->max_len); strmemccpy(buf,(char*)settings[i].setting,len); } break; } /* switch () */ return true; } static bool is_changed(int setting_id) { const struct settings_list *setting = &settings[setting_id]; switch (setting->flags&F_T_MASK) { case F_T_CUSTOM: return setting->custom_setting->is_changed(setting->setting, setting->default_val.custom); break; case F_T_INT: case F_T_UINT: if (setting->flags&F_DEF_ISFUNC) { if (*(int*)setting->setting == setting->default_val.func()) return false; } else if (setting->flags&F_T_SOUND) { if (*(int*)setting->setting == sound_default(setting->sound_setting->setting)) return false; } else if (*(int*)setting->setting == setting->default_val.int_) return false; break; case F_T_BOOL: if (*(bool*)setting->setting == setting->default_val.bool_) return false; break; case F_T_CHARPTR: case F_T_UCHARPTR: if (!strcmp((char*)setting->setting, setting->default_val.charptr)) return false; break; } return true; } static bool settings_write_config(const char* filename, int options) { int i; int fd; char value[MAX_PATH]; fd = open(filename,O_CREAT|O_TRUNC|O_WRONLY, 0666); if (fd < 0) return false; fdprintf(fd, "# .cfg file created by rockbox %s - " "http://www.rockbox.org\r\n\r\n", rbversion); for(i=0; i global_status.topruntime ) global_status.topruntime = global_status.runtime; } void status_save(void) { update_runtime(); register_storage_idle_func(flush_global_status_callback); } int settings_save(void) { update_runtime(); register_storage_idle_func(flush_config_block_callback); return 0; } bool settings_save_config(int options) { /* if we have outstanding temp files it would be a good idea to flush them before the user starts saving things */ rename_temp_file(NVRAM_FILE_TEMP, NVRAM_FILE, NULL); /* dont overwrite .old */ rename_temp_file(CONFIGFILE_TEMP, CONFIGFILE, NULL); /* files from last boot */ char filename[MAX_PATH]; const char *folder, *namebase; switch (options) { case SETTINGS_SAVE_THEME: folder = THEME_DIR; namebase = "theme"; break; #ifdef HAVE_RECORDING case SETTINGS_SAVE_RECPRESETS: folder = RECPRESETS_DIR; namebase = "recording"; break; #endif case SETTINGS_SAVE_EQPRESET: folder = EQS_DIR; namebase = "eq"; break; case SETTINGS_SAVE_SOUND: folder = ROCKBOX_DIR; namebase = "sound"; break; default: folder = ROCKBOX_DIR; namebase = "config"; break; } create_numbered_filename(filename, folder, namebase, ".cfg", 2 IF_CNFN_NUM_(, NULL)); /* allow user to modify filename */ while (true) { if (!kbd_input(filename, sizeof(filename), NULL)) { break; } else { return false; } } if (settings_write_config(filename, options)) splash(HZ, ID2P(LANG_SETTINGS_SAVED)); else splash(HZ, ID2P(LANG_FAILED)); return true; } /** Apply and Reset settings **/ /* * Applies the range infos stored in global_settings to * the peak meter. */ void settings_apply_pm_range(void) { int pm_min, pm_max; /* depending on the scale mode (dBfs or percent) the values of global_settings.peak_meter_dbfs have different meanings */ if (global_settings.peak_meter_dbfs) { /* convert to dBfs * 100 */ pm_min = -(((int)global_settings.peak_meter_min) * 100); pm_max = -(((int)global_settings.peak_meter_max) * 100); } else { /* percent is stored directly -> no conversion */ pm_min = global_settings.peak_meter_min; pm_max = global_settings.peak_meter_max; } /* apply the range */ peak_meter_init_range(global_settings.peak_meter_dbfs, pm_min, pm_max); } void sound_settings_apply(void) { #ifdef AUDIOHW_HAVE_BASS sound_set(SOUND_BASS, global_settings.bass); #endif #ifdef AUDIOHW_HAVE_TREBLE sound_set(SOUND_TREBLE, global_settings.treble); #endif sound_set(SOUND_BALANCE, global_settings.balance); #ifndef PLATFORM_HAS_VOLUME_CHANGE sound_set(SOUND_VOLUME, global_settings.volume); #endif sound_set(SOUND_CHANNELS, global_settings.channel_config); sound_set(SOUND_STEREO_WIDTH, global_settings.stereo_width); #ifdef AUDIOHW_HAVE_BASS_CUTOFF sound_set(SOUND_BASS_CUTOFF, global_settings.bass_cutoff); #endif #ifdef AUDIOHW_HAVE_TREBLE_CUTOFF sound_set(SOUND_TREBLE_CUTOFF, global_settings.treble_cutoff); #endif #ifdef AUDIOHW_HAVE_DEPTH_3D sound_set(SOUND_DEPTH_3D, global_settings.depth_3d); #endif #ifdef AUDIOHW_HAVE_FILTER_ROLL_OFF sound_set(SOUND_FILTER_ROLL_OFF, global_settings.roll_off); #endif #ifdef AUDIOHW_HAVE_POWER_MODE sound_set(SOUND_POWER_MODE, global_settings.power_mode); #endif #ifdef AUDIOHW_HAVE_EQ int b; for (b = 0; b < AUDIOHW_EQ_BAND_NUM; b++) { int setting = sound_enum_hw_eq_band_setting(b, AUDIOHW_EQ_GAIN); sound_set(setting, global_settings.hw_eq_bands[b].gain); #ifdef AUDIOHW_HAVE_EQ_FREQUENCY setting = sound_enum_hw_eq_band_setting(b, AUDIOHW_EQ_FREQUENCY); if (setting != -1) sound_set(setting, global_settings.hw_eq_bands[b].frequency); #endif /* AUDIOHW_HAVE_EQ_FREQUENCY */ #ifdef AUDIOHW_HAVE_EQ_WIDTH setting = sound_enum_hw_eq_band_setting(b, AUDIOHW_EQ_WIDTH); if (setting != -1) sound_set(setting, global_settings.hw_eq_bands[b].width); #endif /* AUDIOHW_HAVE_EQ_WIDTH */ } #endif } void settings_apply(bool read_disk) { int rc; CHART(">set_codepage"); set_codepage(global_settings.default_codepage); CHART(" 0 /* only call if it's really exchangable */ set_battery_capacity(global_settings.battery_capacity); #endif #if BATTERY_TYPES_COUNT > 1 set_battery_type(global_settings.battery_type); #endif #ifdef HAVE_LCD_INVERT lcd_set_invert_display(global_settings.invert); #endif #ifdef HAVE_LCD_FLIP lcd_set_flip(global_settings.flip_display); button_set_flip(global_settings.flip_display); #endif lcd_update(); /* refresh after flipping the screen */ settings_apply_pm_range(); peak_meter_init_times( global_settings.peak_meter_release, global_settings.peak_meter_hold, global_settings.peak_meter_clip_hold); #ifdef HAVE_SPEAKER audio_enable_speaker(global_settings.speaker_mode); #endif if (read_disk) { char buf[MAX_PATH]; /* fonts need to be loaded before the WPS */ if (global_settings.font_file[0] && global_settings.font_file[0] != '-') { int font_ui = screens[SCREEN_MAIN].getuifont(); const char* loaded_font = font_filename(font_ui); snprintf(buf, sizeof buf, FONT_DIR "/%s.fnt", global_settings.font_file); if (!loaded_font || strcmp(loaded_font, buf)) { CHART2(">font_load ", global_settings.font_file); if (font_ui >= 0) font_unload(font_ui); rc = font_load_ex(buf, 0, global_settings.glyphs_to_cache); CHART2("font_load_remoteui ", global_settings.remote_font_file); if (font_ui >= 0) font_unload(font_ui); rc = font_load(buf); CHART2("load_kbd"); load_kbd(buf); CHART("lang_core_load"); lang_core_load(buf); CHART("talk_init"); talk_init(); /* use voice of same language */ CHART("icons_init"); icons_init(); CHART("read_color_theme_file"); read_color_theme_file(); CHART("viewportmanager_theme_changed"); viewportmanager_theme_changed(THEME_UI_VIEWPORT|THEME_LANGUAGE|THEME_BUTTONBAR); CHART("flags&F_T_MASK) { case F_T_CUSTOM: setting->custom_setting->set_default(setting->setting, setting->default_val.custom); break; case F_T_INT: case F_T_UINT: if (setting->flags&F_DEF_ISFUNC) *(int*)var = setting->default_val.func(); else if (setting->flags&F_T_SOUND) *(int*)var = sound_default(setting->sound_setting->setting); else *(int*)var = setting->default_val.int_; break; case F_T_BOOL: *(bool*)var = setting->default_val.bool_; break; case F_T_CHARPTR: case F_T_UCHARPTR: strmemccpy((char*)var, setting->default_val.charptr, setting->filename_setting->max_len); break; } } void settings_reset(void) { for(int i=0; i FONT_SYSFIXED) { font_unload(screens[i].getuifont()); screens[i].setuifont(FONT_SYSFIXED); screens[i].setfont(FONT_SYSFIXED); } } } /** Changing setting values **/ const struct settings_list* find_setting(const void* variable, int *id) { int i; for(i=0;i maxlen) return; strmemccpy(setting, fptr, len); settings_save(); }