From 4a22708f529e03ce024742470f575dbddc9cec95 Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Tue, 6 Aug 2024 17:15:55 +0200 Subject: wmstatus: move to libertyconf --- wmstatus.c | 242 ++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 192 insertions(+), 50 deletions(-) (limited to 'wmstatus.c') diff --git a/wmstatus.c b/wmstatus.c index 0885d97..56e5409 100644 --- a/wmstatus.c +++ b/wmstatus.c @@ -782,30 +782,122 @@ backend_i3_new (void) // --- Configuration ----------------------------------------------------------- -static struct simple_config_item g_config_table[] = -{ - { "mpd_address", "localhost", "MPD host or socket" }, - { "mpd_service", "6600", "MPD service name or port" }, - { "mpd_password", NULL, "MPD password" }, +static struct config_schema g_config_general[] = +{ + { .name = "command", + .comment = "Command to run for more info", + .type = CONFIG_ITEM_STRING }, + { .name = "sleep_timer", + .comment = "Idle seconds to suspend after", + .type = CONFIG_ITEM_INTEGER }, + {} +}; - { "nut_enabled", "off", "NUT UPS status reading enabled" }, - { "nut_load_thld", "50", "NUT threshold for load display" }, +static struct config_schema g_config_mpd[] = +{ + { .name = "address", + .comment = "MPD host or socket", + .type = CONFIG_ITEM_STRING, + .default_ = "\"localhost\"" }, + { .name = "service", + .comment = "MPD service name or port", + .type = CONFIG_ITEM_STRING, + .default_ = "\"6600\"" }, + { .name = "password", + .comment = "MPD password", + .type = CONFIG_ITEM_STRING }, + {} +}; + +static struct config_schema g_config_nut[] = +{ + { .name = "enabled", + .comment = "NUT UPS status reading enabled", + .type = CONFIG_ITEM_BOOLEAN, + .default_ = "off" }, + { .name = "load_thld", + .comment = "NUT threshold for load display", + .type = CONFIG_ITEM_INTEGER, + .default_ = "50" }, // This is just a hack because my UPS doesn't report that value; a more // proper way of providing this information would be by making use of the // enhanced configuration format and allowing arbitrary per-UPS overrides - { "nut_load_power", NULL, "ups.realpower.nominal override" }, - - { "command", NULL, "command to run for more info" }, - { "sleep_timer", NULL, "idle seconds to suspend after" }, - { NULL, NULL, NULL } + { .name = "load_power", + .comment = "ups.realpower.nominal fallback", + .type = CONFIG_ITEM_INTEGER }, + {} }; +static void +app_load_config_general (struct config_item *subtree, void *user_data) +{ + config_schema_apply_to_object (g_config_general, subtree, user_data); +} + +static void +app_load_config_mpd (struct config_item *subtree, void *user_data) +{ + config_schema_apply_to_object (g_config_mpd, subtree, user_data); +} + +static void +app_load_config_nut (struct config_item *subtree, void *user_data) +{ + config_schema_apply_to_object (g_config_nut, subtree, user_data); +} + +static struct config +app_make_config (void) +{ + struct config config = config_make (); + config_register_module (&config, "general", app_load_config_general, NULL); + config_register_module (&config, "mpd", app_load_config_mpd, NULL); + config_register_module (&config, "nut", app_load_config_nut, NULL); + + // Bootstrap configuration, so that we can access schema items at all + config_load (&config, config_item_object ()); + return config; +} + +static const char * +get_config_string (struct config_item *root, const char *key) +{ + struct config_item *item = config_item_get (root, key, NULL); + hard_assert (item); + if (item->type == CONFIG_ITEM_NULL) + return NULL; + hard_assert (config_item_type_is_string (item->type)); + return item->value.string.str; +} + +static const int64_t * +get_config_integer (struct config_item *root, const char *key) +{ + struct config_item *item = config_item_get (root, key, NULL); + hard_assert (item); + if (item->type == CONFIG_ITEM_NULL) + return NULL; + hard_assert (item->type == CONFIG_ITEM_INTEGER); + return &item->value.integer; +} + +static const bool * +get_config_boolean (struct config_item *root, const char *key) +{ + struct config_item *item = config_item_get (root, key, NULL); + hard_assert (item); + if (item->type == CONFIG_ITEM_NULL) + return NULL; + hard_assert (item->type == CONFIG_ITEM_BOOLEAN); + return &item->value.boolean; +} + // --- Application ------------------------------------------------------------- struct app_context { - struct str_map config; ///< Program configuration + struct config config; ///< Program configuration struct backend *backend; ///< WM backend Display *dpy; ///< X display handle @@ -923,8 +1015,7 @@ app_context_init (struct app_context *self) { memset (self, 0, sizeof *self); - self->config = str_map_make (free); - simple_config_load_defaults (&self->config, g_config_table); + self->config = app_make_config (); if (!(self->dpy = XkbOpenDisplay (NULL, &self->xkb_base_event_code, NULL, NULL, NULL, NULL))) @@ -966,8 +1057,8 @@ app_context_init (struct app_context *self) static void app_context_free (struct app_context *self) { - str_map_free (&self->config); - if (self->backend) self->backend->destroy (self->backend); + config_free (&self->config); + if (self->backend) self->backend->destroy (self->backend); poller_fd_reset (&self->x_event); cstr_set (&self->layout, NULL); @@ -1426,7 +1517,8 @@ static void on_command_start (void *user_data) { struct app_context *ctx = user_data; - char *command = str_map_find (&ctx->config, "command"); + const char *command = + get_config_string (ctx->config.root, "general.command"); if (!command) return; @@ -1446,7 +1538,7 @@ on_command_start (void *user_data) posix_spawn_file_actions_addclose (&actions, output_pipe[PIPE_WRITE]); pid_t pid = -1; - char *argv[] = { "sh", "-c", command, NULL }; + char *argv[] = { "sh", "-c", (char *) command, NULL }; int result = posix_spawnp (&pid, argv[0], &actions, NULL, argv, environ); posix_spawn_file_actions_destroy (&actions); @@ -1595,7 +1687,7 @@ mpd_on_connected (void *user_data) struct app_context *ctx = user_data; struct mpd_client *c = &ctx->mpd_client; - const char *password = str_map_find (&ctx->config, "mpd_password"); + const char *password = get_config_string (ctx->config.root, "mpd.password"); if (password) { mpd_client_send_command (c, "password", password, NULL); @@ -1642,9 +1734,10 @@ on_mpd_reconnect (void *user_data) c->on_io_hook = mpd_on_io_hook; struct error *e = NULL; + struct config_item *root = ctx->config.root; if (!mpd_client_connect (&ctx->mpd_client, - str_map_find (&ctx->config, "mpd_address"), - str_map_find (&ctx->config, "mpd_service"), &e)) + get_config_string (root, "mpd.address"), + get_config_string (root, "mpd.service"), &e)) { print_error ("%s: %s", "cannot connect to MPD", e->message); error_free (e); @@ -1702,6 +1795,7 @@ nut_process_ups (struct app_context *ctx, struct strv *ups_list, const char *charge = str_map_find (dict, "battery.charge"); const char *runtime = str_map_find (dict, "battery.runtime"); const char *load = str_map_find (dict, "ups.load"); + const char *power = str_map_find (dict, "ups.realpower.nominal"); if (!soft_assert (status && charge && runtime)) return; @@ -1731,24 +1825,25 @@ nut_process_ups (struct app_context *ctx, struct strv *ups_list, strv_append_owned (&items, interval_string (runtime_sec)); // Only show load if it's higher than the threshold so as to not distract - const char *threshold = str_map_find (&ctx->config, "nut_load_thld"); - unsigned long load_n, threshold_n; + struct config_item *root = ctx->config.root; + const int64_t *threshold = get_config_integer (root, "nut.load_thld"); + const int64_t *fallback = get_config_integer (root, "nut.load_power"); + unsigned long load_n, power_n; if (load - && xstrtoul (&load_n, load, 10) - && xstrtoul (&threshold_n, threshold, 10) - && load_n >= threshold_n) + && xstrtoul (&load_n, load, 10) + && load_n >= (unsigned long) *threshold) { struct str item = str_make (); str_append_printf (&item, "load %s%%", load); - const char *power = str_map_find (dict, "ups.realpower.nominal"); - // Override if NUT cannot tell it correctly for whatever reason - if (!power) power = str_map_find (&ctx->config, "nut_load_power"); - - // Approximation of how much electricity the perpihery actually uses - unsigned long power_n; + // Approximation of how much electricity the perpihery actually uses. + // Use fallback if NUT cannot tell it correctly for whatever reason. if (power && xstrtoul (&power_n, power, 10)) - str_append_printf (&item, " (~%luW)", power_n * load_n / 100); + str_append_printf (&item, + " (~%luW)", power_n * load_n / 100); + else if (fallback && *fallback >= 0) + str_append_printf (&item, + " (~%luW)", (unsigned long) *fallback * load_n / 100); strv_append_owned (&items, str_steal (&item)); } @@ -1891,12 +1986,7 @@ static void on_nut_reconnect (void *user_data) { struct app_context *ctx = user_data; - - bool want_nut = false; - if (!set_boolean_if_valid (&want_nut, - str_map_find (&ctx->config, "nut_enabled"))) - print_error ("invalid configuration value for `%s'", "nut_enabled"); - if (!want_nut) + if (!*get_config_boolean (ctx->config.root, "nut.enabled")) return; struct nut_client *c = &ctx->nut_client; @@ -2580,13 +2670,13 @@ on_x_ready (const struct pollfd *pfd, void *user_data) static void init_xlib_events (struct app_context *ctx) { - unsigned long n; - const char *sleep_timer = str_map_find (&ctx->config, "sleep_timer"); + const int64_t *sleep_timer = + get_config_integer (ctx->config.root, "general.sleep_timer"); if (sleep_timer && ctx->idle_counter) { - if (!xstrtoul (&n, sleep_timer, 10) || !n || n > INT_MAX / 1000) + if (*sleep_timer <= 0 || *sleep_timer > INT_MAX / 1000) exit_fatal ("invalid value for the sleep timer"); - XSyncIntToValue (&ctx->idle_timeout, n * 1000); + XSyncIntToValue (&ctx->idle_timeout, *sleep_timer * 1000); set_idle_alarm (ctx, &ctx->idle_alarm_inactive, XSyncPositiveComparison, ctx->idle_timeout); } @@ -2617,7 +2707,55 @@ init_xlib_events (struct app_context *ctx) XkbAllStateComponentsMask, XkbGroupStateMask); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// --- Configuration ----------------------------------------------------------- + +static void +app_load_configuration (struct app_context *ctx) +{ + char *filename = resolve_filename + (PROGRAM_NAME ".conf", resolve_relative_config_filename); + if (!filename) + return; + + struct error *e = NULL; + struct config_item *root = config_read_from_file (filename, &e); + free (filename); + + if (e) + exit_fatal ("error loading configuration: %s", e->message); + + if (root) + { + config_load (&ctx->config, root); + config_schema_call_changed (ctx->config.root); + } +} + +static void +app_save_configuration (struct app_context *ctx, const char *path_hint) +{ + static const char *prolog = + "# " PROGRAM_NAME " " PROGRAM_VERSION " configuration file\n\n"; + + struct str data = str_make (); + str_append (&data, prolog); + config_item_write (ctx->config.root, true, &data); + + struct error *e = NULL; + char *filename = write_configuration_file (path_hint, &data, &e); + str_free (&data); + + if (!filename) + { + print_error ("%s", e->message); + error_free (e); + exit (EXIT_FAILURE); + } + print_status ("configuration written to `%s'", filename); + free (filename); +} + +// --- Signals ----------------------------------------------------------------- static int g_signal_pipe[2]; ///< A pipe used to signal... signals static struct poller_fd g_signal_event; ///< Signal pipe is readable @@ -2681,6 +2819,8 @@ setup_signal_handlers (struct app_context *ctx) poller_fd_set (&g_signal_event, POLLIN); } +// --- Initialisation, event handling ------------------------------------------ + static void poller_timer_init_and_set (struct poller_timer *self, struct poller *poller, poller_timer_fn cb, void *user_data) @@ -2730,8 +2870,12 @@ main (int argc, char *argv[]) i3bar = true; break; case 'w': - call_simple_config_write_default (optarg, g_config_table); + { + // app_context_init() has side-effects. + struct app_context ctx = { .config = app_make_config () }; + app_save_configuration (&ctx, optarg); exit (EXIT_SUCCESS); + } default: print_error ("wrong options"); opt_handler_usage (&oh, stderr); @@ -2746,11 +2890,9 @@ main (int argc, char *argv[]) struct app_context ctx; app_context_init (&ctx); ctx.prefix = argc > 1 ? argv[1] : NULL; - setup_signal_handlers (&ctx); - struct error *e = NULL; - if (!simple_config_update_from_file (&ctx.config, &e)) - exit_fatal ("%s", e->message); + app_load_configuration (&ctx); + setup_signal_handlers (&ctx); poller_timer_init_and_set (&ctx.time_changed, &ctx.poller, on_time_changed, &ctx); -- cgit v1.2.3-54-g00ecf