swayfx/swaynag/config.c
Rolf Vidar Mazunki Hoksaas 271af3d474
enhancement: update version printing format (#254)
* enhancement: update version printing format

as mentioned in #253, version numbers can be confusing if you don't know
or realize you're running SwayFX instead of standard sway.

to fix this issue, I've updated the way we display versions, replacing
"sway version X" to "swayfx version X", suffixed by the sway upstream
version which swayfx is based upon.

while not immediately displaying we come from swayfx in
swaymsg/swaynag/swaybar, we do display we're based off a specific
upstream sway version.

note: this requires updating `meson.build` (currently `meson.build:181`)
each time we rebase into a new sway version.

Closes: Issue #253
Signed-off-by: Mazunki Hoksaas <rolferen@gmail.com>

* reduce linelength

---------

Signed-off-by: Mazunki Hoksaas <rolferen@gmail.com>
2024-02-14 21:29:04 -05:00

456 lines
14 KiB
C

#define _POSIX_C_SOURCE 200809L
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <wordexp.h>
#include <unistd.h>
#include "log.h"
#include "list.h"
#include "swaynag/swaynag.h"
#include "swaynag/types.h"
#include "util.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
static char *read_and_trim_stdin(void) {
char *buffer = NULL, *line = NULL;
size_t buffer_len = 0, line_size = 0;
while (1) {
ssize_t nread = getline(&line, &line_size, stdin);
if (nread == -1) {
if (feof(stdin)) {
break;
} else {
perror("getline");
goto freeline;
}
}
buffer = realloc(buffer, buffer_len + nread + 1);
if (!buffer) {
perror("realloc");
goto freebuf;
}
memcpy(&buffer[buffer_len], line, nread + 1);
buffer_len += nread;
}
free(line);
while (buffer_len && buffer[buffer_len - 1] == '\n') {
buffer[--buffer_len] = '\0';
}
return buffer;
freeline:
free(line);
freebuf:
free(buffer);
return NULL;
}
int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
list_t *types, struct swaynag_type *type, char **config, bool *debug) {
enum type_options {
TO_COLOR_BACKGROUND = 256,
TO_COLOR_BORDER,
TO_COLOR_BORDER_BOTTOM,
TO_COLOR_BUTTON,
TO_COLOR_DETAILS,
TO_COLOR_TEXT,
TO_COLOR_BUTTON_TEXT,
TO_THICK_BAR_BORDER,
TO_PADDING_MESSAGE,
TO_THICK_DET_BORDER,
TO_THICK_BTN_BORDER,
TO_GAP_BTN,
TO_GAP_BTN_DISMISS,
TO_MARGIN_BTN_RIGHT,
TO_PADDING_BTN,
};
static const struct option opts[] = {
{"button", required_argument, NULL, 'b'},
{"button-no-terminal", required_argument, NULL, 'B'},
{"button-dismiss", required_argument, NULL, 'z'},
{"button-dismiss-no-terminal", required_argument, NULL, 'Z'},
{"config", required_argument, NULL, 'c'},
{"debug", no_argument, NULL, 'd'},
{"edge", required_argument, NULL, 'e'},
{"layer", required_argument, NULL, 'y'},
{"font", required_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"detailed-message", no_argument, NULL, 'l'},
{"detailed-button", required_argument, NULL, 'L'},
{"message", required_argument, NULL, 'm'},
{"output", required_argument, NULL, 'o'},
{"dismiss-button", required_argument, NULL, 's'},
{"type", required_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'},
{"background", required_argument, NULL, TO_COLOR_BACKGROUND},
{"border", required_argument, NULL, TO_COLOR_BORDER},
{"border-bottom", required_argument, NULL, TO_COLOR_BORDER_BOTTOM},
{"button-background", required_argument, NULL, TO_COLOR_BUTTON},
{"text", required_argument, NULL, TO_COLOR_TEXT},
{"button-text", required_argument, NULL, TO_COLOR_BUTTON_TEXT},
{"border-bottom-size", required_argument, NULL, TO_THICK_BAR_BORDER},
{"message-padding", required_argument, NULL, TO_PADDING_MESSAGE},
{"details-border-size", required_argument, NULL, TO_THICK_DET_BORDER},
{"details-background", required_argument, NULL, TO_COLOR_DETAILS},
{"button-border-size", required_argument, NULL, TO_THICK_BTN_BORDER},
{"button-gap", required_argument, NULL, TO_GAP_BTN},
{"button-dismiss-gap", required_argument, NULL, TO_GAP_BTN_DISMISS},
{"button-margin-right", required_argument, NULL, TO_MARGIN_BTN_RIGHT},
{"button-padding", required_argument, NULL, TO_PADDING_BTN},
{0, 0, 0, 0}
};
const char *usage =
"Usage: swaynag [options...]\n"
"\n"
" -b, --button <text> <action> Create a button with text that "
"executes action in a terminal when pressed. Multiple buttons can "
"be defined.\n"
" -B, --button-no-terminal <text> <action> Like --button, but does"
"not run the action in a terminal.\n"
" -z, --button-dismiss <text> <action> Create a button with text that "
"dismisses swaynag, and executes action in a terminal when pressed. "
"Multiple buttons can be defined.\n"
" -Z, --button-dismiss-no-terminal <text> <action> Like "
"--button-dismiss, but does not run the action in a terminal.\n"
" -c, --config <path> Path to config file.\n"
" -d, --debug Enable debugging.\n"
" -e, --edge top|bottom Set the edge to use.\n"
" -y, --layer overlay|top|bottom|background\n"
" Set the layer to use.\n"
" -f, --font <font> Set the font to use.\n"
" -h, --help Show help message and quit.\n"
" -l, --detailed-message Read a detailed message from stdin.\n"
" -L, --detailed-button <text> Set the text of the detail button.\n"
" -m, --message <msg> Set the message text.\n"
" -o, --output <output> Set the output to use.\n"
" -s, --dismiss-button <text> Set the dismiss button text.\n"
" -t, --type <type> Set the message type.\n"
" -v, --version Show the version number and quit.\n"
"\n"
"The following appearance options can also be given:\n"
" --background RRGGBB[AA] Background color.\n"
" --border RRGGBB[AA] Border color.\n"
" --border-bottom RRGGBB[AA] Bottom border color.\n"
" --button-background RRGGBB[AA] Button background color.\n"
" --text RRGGBB[AA] Text color.\n"
" --button-text RRGGBB[AA] Button text color.\n"
" --border-bottom-size size Thickness of the bar border.\n"
" --message-padding padding Padding for the message.\n"
" --details-border-size size Thickness for the details border.\n"
" --details-background RRGGBB[AA] Details background color.\n"
" --button-border-size size Thickness for the button border.\n"
" --button-gap gap Size of the gap between buttons\n"
" --button-dismiss-gap gap Size of the gap for dismiss button.\n"
" --button-margin-right margin Margin from dismiss button to edge.\n"
" --button-padding padding Padding for the button text.\n";
optind = 1;
while (1) {
int c = getopt_long(argc, argv, "b:B:z:Z:c:de:y:f:hlL:m:o:s:t:v", opts, NULL);
if (c == -1) {
break;
}
switch (c) {
case 'b': // Button
case 'B': // Button (No Terminal)
case 'z': // Button (Dismiss)
case 'Z': // Button (Dismiss, No Terminal)
if (swaynag) {
if (optind >= argc) {
fprintf(stderr, "Missing action for button %s\n", optarg);
return EXIT_FAILURE;
}
struct swaynag_button *button = calloc(sizeof(struct swaynag_button), 1);
if (!button) {
perror("calloc");
return EXIT_FAILURE;
}
button->text = strdup(optarg);
button->type = SWAYNAG_ACTION_COMMAND;
button->action = strdup(argv[optind]);
button->terminal = c == 'b';
button->dismiss = c == 'z' || c == 'Z';
list_add(swaynag->buttons, button);
}
optind++;
break;
case 'c': // Config
if (config) {
*config = strdup(optarg);
}
break;
case 'd': // Debug
if (debug) {
*debug = true;
}
break;
case 'e': // Edge
if (type) {
if (strcmp(optarg, "top") == 0) {
type->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
| ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
} else if (strcmp(optarg, "bottom") == 0) {
type->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM
| ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
} else {
fprintf(stderr, "Invalid edge: %s\n", optarg);
return EXIT_FAILURE;
}
}
break;
case 'y': // Layer
if (type) {
if (strcmp(optarg, "background") == 0) {
type->layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;
} else if (strcmp(optarg, "bottom") == 0) {
type->layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
} else if (strcmp(optarg, "top") == 0) {
type->layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
} else if (strcmp(optarg, "overlay") == 0) {
type->layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
} else {
fprintf(stderr, "Invalid layer: %s\n"
"Usage: --layer overlay|top|bottom|background\n",
optarg);
return EXIT_FAILURE;
}
}
break;
case 'f': // Font
if (type) {
free(type->font);
pango_font_description_free(type->font_description);
type->font = strdup(optarg);
type->font_description = pango_font_description_from_string(type->font);
}
break;
case 'l': // Detailed Message
if (swaynag) {
free(swaynag->details.message);
swaynag->details.message = read_and_trim_stdin();
if (!swaynag->details.message) {
return EXIT_FAILURE;
}
swaynag->details.button_up.text = strdup("");
swaynag->details.button_down.text = strdup("");
}
break;
case 'L': // Detailed Button Text
if (swaynag) {
free(swaynag->details.button_details.text);
swaynag->details.button_details.text = strdup(optarg);
}
break;
case 'm': // Message
if (swaynag) {
free(swaynag->message);
swaynag->message = strdup(optarg);
}
break;
case 'o': // Output
if (type) {
free(type->output);
type->output = strdup(optarg);
}
break;
case 's': // Dismiss Button Text
if (swaynag) {
struct swaynag_button *button_close = swaynag->buttons->items[0];
free(button_close->text);
button_close->text = strdup(optarg);
}
break;
case 't': // Type
if (swaynag) {
swaynag->type = swaynag_type_get(types, optarg);
if (!swaynag->type) {
fprintf(stderr, "Unknown type %s\n", optarg);
return EXIT_FAILURE;
}
}
break;
case 'v': // Version
printf("swaynag version " SWAY_VERSION " (based on sway " SWAY_ORIGINAL_VERSION ")\n");
return -1;
case TO_COLOR_BACKGROUND: // Background color
if (type && !parse_color(optarg, &type->background)) {
fprintf(stderr, "Invalid background color: %s", optarg);
}
break;
case TO_COLOR_BORDER: // Border color
if (type && !parse_color(optarg, &type->border)) {
fprintf(stderr, "Invalid border color: %s", optarg);
}
break;
case TO_COLOR_BORDER_BOTTOM: // Bottom border color
if (type && !parse_color(optarg, &type->border_bottom)) {
fprintf(stderr, "Invalid border bottom color: %s", optarg);
}
break;
case TO_COLOR_BUTTON: // Button background color
if (type && !parse_color(optarg, &type->button_background)) {
fprintf(stderr, "Invalid button background color: %s", optarg);
}
break;
case TO_COLOR_DETAILS: // Details background color
if (type && !parse_color(optarg, &type->details_background)) {
fprintf(stderr, "Invalid details background color: %s", optarg);
}
break;
case TO_COLOR_TEXT: // Text color
if (type && !parse_color(optarg, &type->text)) {
fprintf(stderr, "Invalid text color: %s", optarg);
}
break;
case TO_COLOR_BUTTON_TEXT: // Button text color
if (type && !parse_color(optarg, &type->button_text)) {
fprintf(stderr, "Invalid button text color: %s", optarg);
}
break;
case TO_THICK_BAR_BORDER: // Bottom border thickness
if (type) {
type->bar_border_thickness = strtol(optarg, NULL, 0);
}
break;
case TO_PADDING_MESSAGE: // Message padding
if (type) {
type->message_padding = strtol(optarg, NULL, 0);
}
break;
case TO_THICK_DET_BORDER: // Details border thickness
if (type) {
type->details_border_thickness = strtol(optarg, NULL, 0);
}
break;
case TO_THICK_BTN_BORDER: // Button border thickness
if (type) {
type->button_border_thickness = strtol(optarg, NULL, 0);
}
break;
case TO_GAP_BTN: // Gap between buttons
if (type) {
type->button_gap = strtol(optarg, NULL, 0);
}
break;
case TO_GAP_BTN_DISMISS: // Gap between dismiss button
if (type) {
type->button_gap_close = strtol(optarg, NULL, 0);
}
break;
case TO_MARGIN_BTN_RIGHT: // Margin on the right side of button area
if (type) {
type->button_margin_right = strtol(optarg, NULL, 0);
}
break;
case TO_PADDING_BTN: // Padding for the button text
if (type) {
type->button_padding = strtol(optarg, NULL, 0);
}
break;
default: // Help or unknown flag
fprintf(c == 'h' ? stdout : stderr, "%s", usage);
return -1;
}
}
return 0;
}
static bool file_exists(const char *path) {
return path && access(path, R_OK) != -1;
}
char *swaynag_get_config_path(void) {
static const char *config_paths[] = {
"$HOME/.swaynag/config",
"$XDG_CONFIG_HOME/swaynag/config",
SYSCONFDIR "/swaynag/config",
};
char *config_home = getenv("XDG_CONFIG_HOME");
if (!config_home || config_home[0] == '\0') {
config_paths[1] = "$HOME/.config/swaynag/config";
}
wordexp_t p;
for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) {
if (wordexp(config_paths[i], &p, 0) == 0) {
char *path = strdup(p.we_wordv[0]);
wordfree(&p);
if (file_exists(path)) {
return path;
}
free(path);
}
}
return NULL;
}
int swaynag_load_config(char *path, struct swaynag *swaynag, list_t *types) {
FILE *config = fopen(path, "r");
if (!config) {
fprintf(stderr, "Failed to read config. Running without it.\n");
return 0;
}
struct swaynag_type *type = swaynag_type_new("<config>");
list_add(types, type);
char *line = NULL;
size_t line_size = 0;
ssize_t nread;
int line_number = 0;
int result = 0;
while ((nread = getline(&line, &line_size, config)) != -1) {
line_number++;
if (!*line || line[0] == '\n' || line[0] == '#') {
continue;
}
if (line[nread - 1] == '\n') {
line[nread - 1] = '\0';
}
if (line[0] == '[') {
char *close = strchr(line, ']');
if (!close || close != &line[nread - 2] || nread <= 3) {
fprintf(stderr, "Line %d is malformed\n", line_number);
result = 1;
break;
}
*close = '\0';
type = swaynag_type_get(types, &line[1]);
if (!type) {
type = swaynag_type_new(&line[1]);
list_add(types, type);
}
} else {
char *flag = malloc(nread + 3);
if (!flag) {
perror("calloc");
return EXIT_FAILURE;
}
snprintf(flag, nread + 3, "--%s", line);
char *argv[] = {"swaynag", flag};
result = swaynag_parse_options(2, argv, swaynag, types, type,
NULL, NULL);
free(flag);
if (result != 0) {
break;
}
}
}
free(line);
fclose(config);
return result;
}