diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a834b7..783ffef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ (https://codeberg.org/dnkl/yambar/issues/139). * mem: a module handling system memory monitoring * cpu: a module offering cpu usage monitoring +* temp: a module displaying the temperature of the thermal zones in the machine. ### Changed diff --git a/doc/yambar-modules-temp.5.scd b/doc/yambar-modules-temp.5.scd new file mode 100644 index 0000000..3f01359 --- /dev/null +++ b/doc/yambar-modules-temp.5.scd @@ -0,0 +1,50 @@ +yambar-modules-temp(5) + +# NAME +temp - This module displays the temperature of the thermal zones available on +the system + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| temp +: string +: Current temperature of a given thermal zone + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| interval +: int +: no +: Refresh interval of the temperature in ms (default=500). Cannot be less then 500 ms +| unit +: string +: no +: Unit of the temperature. It can be Celsius (C) or Farenheit (F) (default=C) + +| thermal_zone +: int +: yes +: Identifier of the thermal zone to read + +# EXAMPLES + +``` +bar: + left: + - temp: + interval: 1000 + thermal_zone: 0 + content: + string: {text: "{temp}C"} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) diff --git a/modules/meson.build b/modules/meson.build index 192b094..6503e4b 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -19,6 +19,7 @@ mod_data = { 'clock': [[], []], 'cpu': [[], []], 'mem': [[], []], + 'temp': [[], []], 'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json]], 'label': [[], []], 'network': [[], []], diff --git a/modules/temp.c b/modules/temp.c new file mode 100644 index 0000000..3df46fa --- /dev/null +++ b/modules/temp.c @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_MODULE "temp" +#define LOG_ENABLE_DBG 0 +#define SMALLEST_INTERVAL 500 +#include "../bar/bar.h" +#include "../config-verify.h" +#include "../config.h" +#include "../log.h" +#include "../plugin.h" + +enum temp_unit { TEMP_UNIT_INVALID, TEMP_UNIT_CELSIUS, TEMP_UNIT_FAHRENHEIT }; + +struct private +{ + struct particle *label; + uint16_t interval; + uint16_t thermal_zone; + enum temp_unit unit; +}; + +static void +destroy(struct module *mod) +{ + struct private *m = mod->private; + m->label->destroy(m->label); + free(m); + module_default_destroy(mod); +} + +static const char * +description(struct module *mod) +{ + return "temp"; +} + +static bool +get_temp(uint16_t thermal_zone, enum temp_unit unit, double *temp) +{ + FILE *fp = NULL; + char *line = NULL; + size_t len = 0; + int32_t read_temp = 0; + bool res = false; + + ssize_t filename_len = snprintf(NULL, 0, "/sys/class/thermal/thermal_zone%i/temp", thermal_zone); + char *filename = malloc(filename_len + 1); + snprintf(filename, filename_len + 1, "/sys/class/thermal/thermal_zone%i/temp", thermal_zone); + + fp = fopen(filename, "r"); + if (NULL == fp) { + LOG_ERRNO("unable to open /sys/class/thermal/thermal_zone%i/temp", thermal_zone); + goto exit; + } + + if (-1 == getline(&line, &len, fp)) { + LOG_ERRNO("unable to get temperature for thermal zone %i", thermal_zone); + goto exit; + } + + if (1 != sscanf(line, "%" SCNi32, &read_temp)) { + LOG_ERRNO("unable to get temperature for thermal zone %i", thermal_zone); + goto exit; + } + + switch (unit) { + case TEMP_UNIT_CELSIUS: + *temp = read_temp / 1000; + break; + case TEMP_UNIT_FAHRENHEIT: + *temp = read_temp; + *temp = (*temp * (9 / 5)) + 32; + break; + default: + goto exit; + break; + } + res = true; + +exit: + free(line); + fclose(fp); + + free(filename); + return res; +} + +static struct exposable * +content(struct module *mod) +{ + const struct private *p = mod->private; + double temp = 0; + + if (!get_temp(p->thermal_zone, p->unit, &temp)) { + LOG_ERR("unable to retrieve the temperature"); + } + + struct tag_set tags = { + .tags = (struct tag *[]){tag_new_int(mod, "temp", round(temp))}, + .count = 1, + }; + + struct exposable *exposable = p->label->instantiate(p->label, &tags); + tag_set_destroy(&tags); + return exposable; +} + +#include +static int +run(struct module *mod) +{ + const struct bar *bar = mod->bar; + bar->refresh(bar); + struct private *p = mod->private; + while (true) { + struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; + + int res = poll(fds, 1, p->interval); + + if (res < 0) { + if (EINTR == errno) + continue; + LOG_ERRNO("unable to poll abort fd"); + return -1; + } + + if (fds[0].revents & POLLIN) + break; + + bar->refresh(bar); + } + + return 0; +} + +static enum temp_unit +str_to_unit(const char *unit_str) +{ + if (0 == strcasecmp(unit_str, "C")) { + return TEMP_UNIT_CELSIUS; + } + + if (0 == strcasecmp(unit_str, "F")) { + return TEMP_UNIT_FAHRENHEIT; + } + + return TEMP_UNIT_INVALID; +} + +static struct module * +temp_new(uint16_t interval, uint16_t thermal_zone, enum temp_unit unit, struct particle *label) +{ + struct private *p = calloc(1, sizeof(*p)); + p->label = label; + p->interval = interval; + p->unit = unit; + + struct module *mod = module_common_new(); + mod->private = p; + mod->run = &run; + mod->destroy = &destroy; + mod->content = &content; + mod->description = &description; + return mod; +} + +static struct module * +from_conf(const struct yml_node *node, struct conf_inherit inherited) +{ + const struct yml_node *interval = yml_get_value(node, "interval"); + const struct yml_node *unit = yml_get_value(node, "unit"); + const struct yml_node *thermal_zone = yml_get_value(node, "thermal_zone"); + const struct yml_node *c = yml_get_value(node, "content"); + + return temp_new(interval == NULL ? SMALLEST_INTERVAL : yml_value_as_int(interval), yml_value_as_int(thermal_zone), + unit == NULL ? TEMP_UNIT_CELSIUS : str_to_unit(yml_value_as_string(unit)), + conf_to_particle(c, inherited)); +} + +static bool +conf_verify_interval(keychain_t *chain, const struct yml_node *node) +{ + if (!conf_verify_unsigned(chain, node)) + return false; + + if (yml_value_as_int(node) < SMALLEST_INTERVAL) { + LOG_ERR("%s: interval value cannot be less than %d ms", conf_err_prefix(chain, node), SMALLEST_INTERVAL); + return false; + } + + return true; +} + +static bool +conf_verify_unit(keychain_t *chain, const struct yml_node *node) +{ + if (!conf_verify_string(chain, node)) + return false; + + enum temp_unit unit = str_to_unit(yml_value_as_string(node)); + if (unit == TEMP_UNIT_INVALID) { + LOG_ERR("%s: invalid unit, must be C or F", conf_err_prefix(chain, node)); + return false; + } + + return true; +} + +static bool +verify_conf(keychain_t *chain, const struct yml_node *node) +{ + static const struct attr_info attrs[] = { + {"interval", false, &conf_verify_interval}, + {"thermal_zone", true, &conf_verify_unsigned}, + {"unit", false, &conf_verify_unit}, + MODULE_COMMON_ATTRS, + }; + + return conf_verify_dict(chain, node, attrs); +} + +const struct module_iface module_temp_iface = { + .verify_conf = &verify_conf, + .from_conf = &from_conf, +}; + +#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) +extern const struct module_iface iface __attribute__((weak, alias("module_temp_iface"))); +#endif diff --git a/plugin.c b/plugin.c index 1faae0e..53f2d9b 100644 --- a/plugin.c +++ b/plugin.c @@ -51,6 +51,7 @@ EXTERN_MODULE(xkb); EXTERN_MODULE(xwindow); EXTERN_MODULE(cpu); EXTERN_MODULE(mem); +EXTERN_MODULE(temp); EXTERN_PARTICLE(empty); EXTERN_PARTICLE(list); @@ -140,6 +141,7 @@ init(void) #endif REGISTER_CORE_MODULE(mem, mem); REGISTER_CORE_MODULE(cpu, cpu); + REGISTER_CORE_MODULE(temp, temp); REGISTER_CORE_PARTICLE(empty, empty); REGISTER_CORE_PARTICLE(list, list);