/** * @file mce-conf.c * Configuration option handling for MCE *

* Copyright © 2006-2009 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2013-2019 Jolla Ltd. *

* @author David Weinehall * @author Santtu Lakkala * @author Simo Piiroinen * * mce is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * mce 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mce. If not, see . */ #include "mce-conf.h" #include "mce.h" #include "mce-log.h" #include "modules/led.h" #include #include /** Pointer to the keyfile structure where config values are read from */ static gpointer keyfile = NULL; /** Internal helper for insuring valid keyfile pointer is available * * @param keyfilepointer custom key file, or NULL to use the default one * * @returns non-null keyfile pointer, or aborts */ static gpointer mce_conf_get_keyfile(void) { if( !keyfile ) { /* Earlier it was possible to have mce running with NULL * keyfile. Now the only reasons that might happen are: * 1) mce_conf_init() was not called yet * 2) mce_conf_init() has failed * 3) mce_conf_exit() has already been called * i.e. critical logic errors somewhere */ mce_log(LL_CRIT, "mce config subsystem used without " "properly initializing it"); mce_abort(); } return keyfile; } /** Check if configuration group is available * * @param group The configuration group * * @return TRUE if group is available, FALSE otherwise */ gboolean mce_conf_has_group(const gchar *group) { gpointer keyfileptr = mce_conf_get_keyfile(); return g_key_file_has_group(keyfileptr, group); } /** Check if configuration key is available * * @param group The configuration group * @param key The configuration key * * @return TRUE if key is available, FALSE otherwise */ gboolean mce_conf_has_key(const gchar *group, const gchar *key) { gpointer keyfileptr = mce_conf_get_keyfile(); GError *error = NULL; gboolean res = g_key_file_has_key(keyfileptr, group, key, &error); g_clear_error(&error); return res; } /** * Get a boolean configuration value * * @param group The configuration group to get the value from * @param key The configuration key to get the value of * @param defaultval The default value to use if the key isn't set * @param keyfileptr A keyfile pointer, or NULL to use the default keyfile * @return The configuration value on success, the default value on failure */ gboolean mce_conf_get_bool(const gchar *group, const gchar *key, const gboolean defaultval) { gboolean tmp = FALSE; GError *error = NULL; gpointer keyfileptr = mce_conf_get_keyfile(); tmp = g_key_file_get_boolean(keyfileptr, group, key, &error); if (error != NULL) { mce_log(LL_DEBUG, "Could not get config key %s/%s; %s; " "defaulting to `%d'", group, key, error->message, defaultval); tmp = defaultval; } g_clear_error(&error); return tmp; } /** * Get an integer configuration value * * @param group The configuration group to get the value from * @param key The configuration key to get the value of * @param defaultval The default value to use if the key isn't set * @param keyfileptr A keyfile pointer, or NULL to use the default keyfile * @return The configuration value on success, the default value on failure */ gint mce_conf_get_int(const gchar *group, const gchar *key, const gint defaultval) { gint tmp = -1; GError *error = NULL; gpointer keyfileptr = mce_conf_get_keyfile(); tmp = g_key_file_get_integer(keyfileptr, group, key, &error); if (error != NULL) { mce_log(LL_DEBUG, "Could not get config key %s/%s; %s; " "defaulting to `%d'", group, key, error->message, defaultval); tmp = defaultval; } g_clear_error(&error); return tmp; } /** * Get an integer list configuration value * * @param group The configuration group to get the value from * @param key The configuration key to get the value of * @param length The length of the list, or NULL if not needed * @param keyfileptr A keyfile pointer, or NULL to use the default keyfile * @return The configuration value on success, NULL on failure */ gint *mce_conf_get_int_list(const gchar *group, const gchar *key, gsize *length) { gint *tmp = NULL; GError *error = NULL; gpointer keyfileptr = mce_conf_get_keyfile(); tmp = g_key_file_get_integer_list(keyfileptr, group, key, length, &error); if (error != NULL) { mce_log(LL_DEBUG, "Could not get config key %s/%s; %s", group, key, error->message); if( length ) *length = 0; } g_clear_error(&error); return tmp; } /** * Get a string configuration value * * @param group The configuration group to get the value from * @param key The configuration key to get the value of * @param defaultval The default value to use if the key isn't set * @param keyfileptr A keyfile pointer, or NULL to use the default keyfile * @return The configuration value on success, the default value on failure */ gchar *mce_conf_get_string(const gchar *group, const gchar *key, const gchar *defaultval) { gchar *tmp = NULL; GError *error = NULL; gpointer keyfileptr = mce_conf_get_keyfile(); tmp = g_key_file_get_string(keyfileptr, group, key, &error); if (error != NULL) { mce_log(LL_DEBUG, "Could not get config key %s/%s; %s; %s%s%s", group, key, error->message, defaultval ? "defaulting to `" : "no default set", defaultval ? defaultval : "", defaultval ? "'" : ""); if (defaultval != NULL) tmp = g_strdup(defaultval); } g_clear_error(&error); return tmp; } /** * Get a string list configuration value * * @param group The configuration group to get the value from * @param key The configuration key to get the value of * @param length The length of the list, or NULL if not needed * @param keyfileptr A keyfile pointer, or NULL to use the default keyfile * @return The configuration value on success, NULL on failure */ gchar **mce_conf_get_string_list(const gchar *group, const gchar *key, gsize *length) { gchar **tmp = NULL; GError *error = NULL; gpointer keyfileptr = mce_conf_get_keyfile(); tmp = g_key_file_get_string_list(keyfileptr, group, key, length, &error); if (error != NULL) { mce_log(LL_DEBUG, "Could not get config key %s/%s; %s", group, key, error->message); if( length ) *length = 0; } g_clear_error(&error); return tmp; } gchar **mce_conf_get_keys(const gchar *group, gsize *length) { gchar **tmp = NULL; GError *error = NULL; gpointer keyfileptr = mce_conf_get_keyfile(); tmp = g_key_file_get_keys(keyfileptr, group, length, &error); if (error != NULL) { mce_log(LL_WARN, "Could not get config keys %s; %s", group, error->message); if( length ) *length = 0; } g_clear_error(&error); return tmp; } /** Copy key value key value from one keyfile to another * * @param dest keyfile to modify * @param srce keyfile to copy from * @param grp value group to copy * @param key value key to copy */ static void mce_conf_override_key(GKeyFile *dest, GKeyFile *srce, const char *grp, const char *key) { gchar *val = g_key_file_get_value(srce, grp, key, 0); if( val ) { //mce_log(LL_NOTICE, "[%s] %s = %s", grp, key, val); g_key_file_set_value(dest, grp, key, val); g_free(val); } } /** Augment key value with data from another file * * @param dest keyfile to modify * @param srce keyfile to add from * @param grp value group to add * @param key value key to add */ static void mce_conf_append_key(GKeyFile *dest, GKeyFile *srce, const char *grp, const char *key) { gchar *val = g_key_file_get_value(srce, grp, key, 0); if( val ) { gchar *old = g_key_file_get_value(dest, grp, key, 0); gchar *tmp = 0; if( old && *old ) { tmp = g_strconcat(val, ";", old, NULL); } //mce_log(LL_NOTICE, "[%s] %s = %s", grp, key, tmp ?: val); g_key_file_set_value(dest, grp, key, tmp ?: val); g_free(tmp); g_free(old); g_free(val); } } /** Merge value from one keyfile to another * * Existing values will be overridden, except for values * in group [evdev] that are appended to existing data. * * @param dest keyfile to modify * @param srce keyfile to merge from * @param grp value group to merge * @param key value key to merge */ static void mce_conf_merge_key(GKeyFile *dest, GKeyFile *srce, const char *grp, const char *key) { /* groups/keys to append instead of overriding */ static const struct { const gchar *grp; const gchar *key; // NULL == every key in the group } lut[] = { { .grp = "evdev", }, { .grp = "modules/display", }, { .grp = MCE_CONF_LED_GROUP, .key = MCE_CONF_LED_PATTERNS_REQUIRED, }, { .grp = MCE_CONF_LED_GROUP, .key = MCE_CONF_LED_PATTERNS_DISABLED, }, { .grp = NULL, } }; for( size_t i = 0; ; ++i ) { if( lut[i].grp == NULL ) { mce_conf_override_key(dest, srce, grp, key); break; } if( strcmp(lut[i].grp, grp) ) continue; if( !lut[i].key || !strcmp(lut[i].key, key) ) { mce_conf_append_key(dest, srce, grp, key); break; } } } /** Merge group of values from one keyfile to another * * @param dest keyfile to modify * @param srce keyfile to merge from * @param grp value group to merge */ static void mce_conf_merge_group(GKeyFile *dest, GKeyFile *srce, const char *grp) { gchar **key = g_key_file_get_keys(srce, grp, 0, 0); if( key ) { for( size_t k = 0; key[k]; ++k ) mce_conf_merge_key(dest, srce, grp, key[k]); g_strfreev(key); } } /** Merge all groups and values from one keyfile to another * * @param dest keyfile to modify * @param srce keyfile to merge from */ static void mce_conf_merge_file(GKeyFile *dest, GKeyFile *srce) { gchar **grp = g_key_file_get_groups(srce, 0); if( grp ) { for( size_t g = 0; grp[g]; ++g ) mce_conf_merge_group(dest, srce, grp[g]); g_strfreev(grp); } } /** Callback function for logging errors within glob() * * @param path path to file/dir where error occurred * @param err errno that occurred * * @return 0 (= do not stop glob) */ static int mce_conf_glob_error_cb(const char *path, int err) { mce_log(LL_WARN, "%s: glob: %s", path, g_strerror(err)); return 0; } /** Process config data from /etc/mce/mce.d/xxx.ini files */ static GKeyFile *mce_conf_read_ini_files(void) { static const char pattern[] = MCE_CONF_DIR"/[0-9][0-9]*.ini"; GKeyFile *ini = g_key_file_new(); glob_t gb; memset(&gb, 0, sizeof gb); if( glob(pattern, 0, mce_conf_glob_error_cb, &gb) != 0 ) { mce_log(LL_WARN, "no mce configuration ini-files found"); goto EXIT; } for( size_t i = 0; i < gb.gl_pathc; ++i ) { const char *path = gb.gl_pathv[i]; GError *err = 0; GKeyFile *tmp = g_key_file_new(); if( !g_key_file_load_from_file(tmp, path, 0, &err) ) { mce_log(LL_WARN, "%s: can't load: %s", path, err->message ?: "unknown"); } else { mce_log(LL_NOTICE, "processing %s ...", path); mce_conf_merge_file(ini, tmp); } g_clear_error(&err); g_key_file_free(tmp); } EXIT: globfree(&gb); return ini; } /* XXX: * We should probably use * /dev/input/keypad * /dev/input/gpio-keys * /dev/input/pwrbutton * /dev/input/ts * and add whitelist entries for misc devices instead */ /** * List of drivers that provide touchscreen events * XXX: If this is made case insensitive, * we could search for "* touchscreen" instead */ static const gchar *const touch_builtin[] = { /** Input layer name for the Atmel mXT touchscreen */ "Atmel mXT Touchscreen", /** Input layer name for the Atmel QT602240 touchscreen */ "Atmel QT602240 Touchscreen", /** TSC2005 touchscreen */ "TSC2005 touchscreen", /** TSC2301 touchscreen */ "TSC2301 touchscreen", /** ADS784x touchscreen */ "ADS784x touchscreen", /** No more entries */ NULL }; /** * List of drivers that provide keyboard events */ static const gchar *const keybd_builtin[] = { /** Input layer name for the TWL4030 keyboard/keypad */ "TWL4030 Keypad", /** Legacy input layer name for the TWL4030 keyboard/keypad */ "omap_twl4030keypad", /** Generic input layer name for keyboard/keypad */ "Internal keyboard", /** Input layer name for the LM8323 keypad */ "LM8323 keypad", /** Generic input layer name for keypad */ "Internal keypad", /** Input layer name for the TSC2301 keypad */ "TSC2301 keypad", /** Legacy generic input layer name for keypad */ "omap-keypad", /** Input layer name for standard PC keyboards */ "AT Translated Set 2 keyboard", /** Input layer name for the power button in various MeeGo devices */ "msic_power_btn", /** Input layer name for the TWL4030 power button */ "twl4030_pwrbutton", /** Input layer name for the Triton 2 power button */ "triton2-pwrbutton", /** Input layer name for the Retu powerbutton */ "retu-pwrbutton", /** Input layer name for the PC Power button */ "Power Button", /** Input layer name for the PC Sleep button */ "Sleep Button", /** Input layer name for the Thinkpad extra buttons */ "Thinkpad Extra Buttons", /** Input layer name for ACPI virtual keyboard */ "ACPI Virtual Keyboard Device", /** Input layer name for GPIO-keys */ "gpio-keys", /** Input layer name for DFL-61/TWL4030 jack sense */ "dfl61-twl4030 Jack", /** Legacy input layer name for TWL4030 jack sense */ "rx71-twl4030 Jack", /** Input layer name for PC Lid switch */ "Lid Switch", /** No more entries */ NULL }; /** * List of drivers that we should not monitor */ static const gchar *const black_builtin[] = { /** Input layer name for the AMI305 magnetometer */ "ami305 magnetometer", /** Input layer name for the ST LIS3LV02DL accelerometer */ "ST LIS3LV02DL Accelerometer", /** Input layer name for the ST LIS302DL accelerometer */ "ST LIS302DL Accelerometer", /** Input layer name for the TWL4030 vibrator */ "twl4030:vibrator", /** Input layer name for AV accessory */ "AV Accessory", /** Input layer name for the video bus */ "Video Bus", /** Input layer name for the PC speaker */ "PC Speaker", /** Input layer name for the Intel HDA headphone */ "HDA Intel Headphone", /** Input layer name for the Intel HDA microphone */ "HDA Intel Mic", /** Input layer name for the UVC 17ef:4807 webcam in thinkpad X301 */ "UVC Camera (17ef:4807)", /** Input layer name for the UVC 17ef:480c webcam in thinkpad X201si */ "UVC Camera (17ef:480c)", /** No more entries */ NULL }; /** List of touchscreen event devices obtained from ini files */ static gchar **touch_cached = NULL; /** List of keyboard event devices obtained from ini files */ static gchar **keybd_cached = NULL; /** List of blacklisted event devices obtained from ini files */ static gchar **black_cached = NULL; /** * Init function for the mce-conf component * * @return TRUE on success, FALSE on failure */ gboolean mce_conf_init(void) { gboolean status = FALSE; if( !(keyfile = mce_conf_read_ini_files()) ) goto EXIT; touch_cached = g_key_file_get_string_list(keyfile, "evdev", "touch", 0, 0); keybd_cached = g_key_file_get_string_list(keyfile, "evdev", "keybd", 0, 0); black_cached = g_key_file_get_string_list(keyfile, "evdev", "black", 0, 0); status = TRUE; EXIT: return status; } /** * Exit function for the mce-conf component */ void mce_conf_exit(void) { g_strfreev(touch_cached), touch_cached = 0; g_strfreev(keybd_cached), keybd_cached = 0; g_strfreev(black_cached), black_cached = 0; if( keyfile ) g_key_file_free(keyfile), keyfile = 0; return; } const gchar * const *mce_conf_get_touchscreen_event_drivers(void) { return (const gchar*const*)touch_cached ?: touch_builtin; } const gchar * const *mce_conf_get_keyboard_event_drivers(void) { return (const gchar*const*)keybd_cached ?: keybd_builtin; } const gchar * const *mce_conf_get_blacklisted_event_drivers(void) { return (const gchar*const*)black_cached ?: black_builtin; }