diff --git a/Makefile.am b/Makefile.am index c2c1c0a..15b8ffb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -50,6 +50,7 @@ i3status_SOURCES = \ i3status.c \ src/auto_detect_format.c \ src/first_network_device.c \ + src/format_placeholders.c \ src/general.c \ src/output.c \ src/print_battery_info.c \ diff --git a/include/i3status.h b/include/i3status.h index 2e314b1..95e6eba 100644 --- a/include/i3status.h +++ b/include/i3status.h @@ -198,6 +198,20 @@ char *endcolor() __attribute__((pure)); void reset_cursor(void); void maybe_escape_markup(char *text, char **buffer); +char *rtrim(const char *s); +char *ltrim(const char *s); +char *trim(const char *s); + +// copied from i3:libi3/format_placeholders.c +/* src/format_placeholders.c */ +typedef struct { + /* The placeholder to be replaced, e.g., "%title". */ + char *name; + /* The value this placeholder should be replaced with. */ + char *value; +} placeholder_t; +char *format_placeholders(const char *format, placeholder_t *placeholders, int num); + /* src/auto_detect_format.c */ char *auto_detect_format(); diff --git a/src/format_placeholders.c b/src/format_placeholders.c new file mode 100644 index 0000000..650089d --- /dev/null +++ b/src/format_placeholders.c @@ -0,0 +1,70 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + */ +// copied from i3:libi3/format_placeholders.c +#include +#include +#include +#include +#include + +#include "i3status.h" + +#ifndef CS_STARTS_WITH +#define CS_STARTS_WITH(string, needle) (strncmp((string), (needle), strlen((needle))) == 0) +#endif + +/* + * Replaces occurrences of the defined placeholders in the format string. + * + */ +char *format_placeholders(const char *format, placeholder_t *placeholders, int num) { + if (format == NULL) + return NULL; + + /* We have to first iterate over the string to see how much buffer space + * we need to allocate. */ + int buffer_len = strlen(format) + 1; + for (const char *walk = format; *walk != '\0'; walk++) { + for (int i = 0; i < num; i++) { + if (!CS_STARTS_WITH(walk, placeholders[i].name)) + continue; + + buffer_len = buffer_len - strlen(placeholders[i].name) + strlen(placeholders[i].value); + walk += strlen(placeholders[i].name) - 1; + break; + } + } + + /* Now we can parse the format string. */ + char buffer[buffer_len]; + char *outwalk = buffer; + for (const char *walk = format; *walk != '\0'; walk++) { + if (*walk != '%') { + *(outwalk++) = *walk; + continue; + } + + bool matched = false; + for (int i = 0; i < num; i++) { + if (!CS_STARTS_WITH(walk, placeholders[i].name)) { + continue; + } + + matched = true; + outwalk += sprintf(outwalk, "%s", placeholders[i].value); + walk += strlen(placeholders[i].name) - 1; + break; + } + + if (!matched) + *(outwalk++) = *walk; + } + + *outwalk = '\0'; + return sstrdup(buffer); +} diff --git a/src/output.c b/src/output.c index 937fa60..9a18049 100644 --- a/src/output.c +++ b/src/output.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "i3status.h" @@ -121,3 +122,36 @@ void maybe_escape_markup(char *text, char **buffer) { } } } + +/* + * remove leading spaces + */ +char *ltrim(const char *s) { + while (isspace(*s)) + ++s; + return sstrdup(s); +} + +/* + * remove trailing spaces + */ +char *rtrim(const char *s) { + char *r = sstrdup(s); + if (r != NULL) { + char *fr = r + strlen(s) - 1; + while ((isspace(*fr) || *fr == 0) && fr >= r) + --fr; + *++fr = 0; + } + return r; +} + +/* + * remove leading & trailing spaces + */ +char *trim(const char *s) { + char *r = rtrim(s); + char *f = ltrim(r); + free(r); + return f; +} \ No newline at end of file diff --git a/src/print_battery_info.c b/src/print_battery_info.c index 1240626..dd3211a 100644 --- a/src/print_battery_info.c +++ b/src/print_battery_info.c @@ -10,6 +10,8 @@ #include "i3status.h" +#define STRING_SIZE 10 + #if defined(__linux__) #include #include @@ -571,7 +573,6 @@ static bool slurp_all_batteries(struct battery_info *batt_info, yajl_gen json_ge } void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char *path, const char *format, const char *format_down, const char *status_chr, const char *status_bat, const char *status_unk, const char *status_full, int low_threshold, char *threshold_type, bool last_full_capacity, const char *format_percentage, bool hide_seconds) { - const char *walk; char *outwalk = buffer; struct battery_info batt_info = { .full_design = -1, @@ -653,94 +654,70 @@ void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char } } -#define EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT() \ - do { \ - if (outwalk == prevoutwalk) { \ - if (outwalk > buffer && isspace((int)outwalk[-1])) \ - outwalk--; \ - else if (isspace((int)*(walk + 1))) \ - walk++; \ - } \ - } while (0) + char string_status[STRING_SIZE]; + char string_percentage[STRING_SIZE]; + // following variables are not alwasy set. If they are not set they should be empty. + char string_remaining[STRING_SIZE] = ""; + char string_emptytime[STRING_SIZE] = ""; + char string_consumption[STRING_SIZE] = ""; - for (walk = format; *walk != '\0'; walk++) { - char *prevoutwalk = outwalk; - - if (*walk != '%') { - *(outwalk++) = *walk; - - } else if (BEGINS_WITH(walk + 1, "status")) { - const char *statusstr; - switch (batt_info.status) { - case CS_CHARGING: - statusstr = status_chr; - break; - case CS_DISCHARGING: - statusstr = status_bat; - break; - case CS_FULL: - statusstr = status_full; - break; - default: - statusstr = status_unk; - } - - outwalk += sprintf(outwalk, "%s", statusstr); - walk += strlen("status"); - - } else if (BEGINS_WITH(walk + 1, "percentage")) { - outwalk += sprintf(outwalk, format_percentage, batt_info.percentage_remaining, pct_mark); - walk += strlen("percentage"); - - } else if (BEGINS_WITH(walk + 1, "remaining")) { - if (batt_info.seconds_remaining >= 0) { - int seconds, hours, minutes; - - hours = batt_info.seconds_remaining / 3600; - seconds = batt_info.seconds_remaining - (hours * 3600); - minutes = seconds / 60; - seconds -= (minutes * 60); - - if (hide_seconds) - outwalk += sprintf(outwalk, "%02d:%02d", - max(hours, 0), max(minutes, 0)); - else - outwalk += sprintf(outwalk, "%02d:%02d:%02d", - max(hours, 0), max(minutes, 0), max(seconds, 0)); - } - walk += strlen("remaining"); - EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT(); - - } else if (BEGINS_WITH(walk + 1, "emptytime")) { - if (batt_info.seconds_remaining >= 0) { - time_t empty_time = time(NULL) + batt_info.seconds_remaining; - set_timezone(NULL); /* Use local time. */ - struct tm *empty_tm = localtime(&empty_time); - - if (hide_seconds) - outwalk += sprintf(outwalk, "%02d:%02d", - max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0)); - else - outwalk += sprintf(outwalk, "%02d:%02d:%02d", - max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0), max(empty_tm->tm_sec, 0)); - } - walk += strlen("emptytime"); - EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT(); - - } else if (BEGINS_WITH(walk + 1, "consumption")) { - if (batt_info.present_rate >= 0) - outwalk += sprintf(outwalk, "%1.2fW", batt_info.present_rate / 1e6); - - walk += strlen("consumption"); - EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT(); - - } else { - *(outwalk++) = '%'; - } + const char *statusstr; + switch (batt_info.status) { + case CS_CHARGING: + statusstr = status_chr; + break; + case CS_DISCHARGING: + statusstr = status_bat; + break; + case CS_FULL: + statusstr = status_full; + break; + default: + statusstr = status_unk; } + snprintf(string_status, STRING_SIZE, "%s", statusstr); + snprintf(string_percentage, STRING_SIZE, format_percentage, batt_info.percentage_remaining, pct_mark); + + if (batt_info.seconds_remaining >= 0) { + int seconds, hours, minutes; + hours = batt_info.seconds_remaining / 3600; + seconds = batt_info.seconds_remaining - (hours * 3600); + minutes = seconds / 60; + seconds -= (minutes * 60); + if (hide_seconds) + snprintf(string_remaining, STRING_SIZE, "%02d:%02d", max(hours, 0), max(minutes, 0)); + else + snprintf(string_remaining, STRING_SIZE, "%02d:%02d:%02d", max(hours, 0), max(minutes, 0), max(seconds, 0)); + } + + if (batt_info.seconds_remaining >= 0) { + time_t empty_time = time(NULL) + batt_info.seconds_remaining; + set_timezone(NULL); /* Use local time. */ + struct tm *empty_tm = localtime(&empty_time); + if (hide_seconds) + snprintf(string_emptytime, STRING_SIZE, "%02d:%02d", max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0)); + else + snprintf(string_emptytime, STRING_SIZE, "%02d:%02d:%02d", max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0), max(empty_tm->tm_sec, 0)); + } + + if (batt_info.present_rate >= 0) + snprintf(string_consumption, STRING_SIZE, "%1.2fW", batt_info.present_rate / 1e6); + + placeholder_t placeholders[] = { + {.name = "%status", .value = string_status}, + {.name = "%percentage", .value = string_percentage}, + {.name = "%remaining", .value = string_remaining}, + {.name = "%emptytime", .value = string_emptytime}, + {.name = "%consumption", .value = string_consumption}}; + + const size_t num = sizeof(placeholders) / sizeof(placeholder_t); + char *untrimmed = format_placeholders(format, &placeholders[0], num); + buffer = trim(untrimmed); + free(untrimmed); if (colorful_output) END_COLOR; OUTPUT_FULL_TEXT(buffer); + free(buffer); } diff --git a/testcases/020-percentliteral-battery/BAT0_uevent b/testcases/020-percentliteral-battery/BAT0_uevent new file mode 100644 index 0000000..b994324 --- /dev/null +++ b/testcases/020-percentliteral-battery/BAT0_uevent @@ -0,0 +1,4 @@ +POWER_SUPPLY_STATUS=Discharging +POWER_SUPPLY_CURRENT_NOW=1107000 +POWER_SUPPLY_CHARGE_FULL_DESIGN=7800000 +POWER_SUPPLY_CHARGE_NOW=2390000 diff --git a/testcases/020-percentliteral-battery/expected_output.txt b/testcases/020-percentliteral-battery/expected_output.txt new file mode 100644 index 0000000..a0c0525 --- /dev/null +++ b/testcases/020-percentliteral-battery/expected_output.txt @@ -0,0 +1 @@ +I can %haz literal% % ? diff --git a/testcases/020-percentliteral-battery/i3status.conf b/testcases/020-percentliteral-battery/i3status.conf new file mode 100644 index 0000000..71a333d --- /dev/null +++ b/testcases/020-percentliteral-battery/i3status.conf @@ -0,0 +1,10 @@ +general { + output_format = "none" +} + +order += "battery all" + +battery all { + format = "I can %haz literal% % ?" + path = "testcases/020-percentliteral-battery/BAT%d_uevent" +}