Add feature: progress bar
This commit is contained in:
parent
93b52df269
commit
bfcf655b1e
12
config.h
12
config.h
@ -6,10 +6,13 @@ struct settings defaults = {
|
|||||||
.markup = MARKUP_NO,
|
.markup = MARKUP_NO,
|
||||||
.colors_norm.bg = "#1793D1",
|
.colors_norm.bg = "#1793D1",
|
||||||
.colors_norm.fg = "#DDDDDD",
|
.colors_norm.fg = "#DDDDDD",
|
||||||
|
.colors_norm.highlight = "#1745d1",
|
||||||
.colors_crit.bg = "#ffaaaa",
|
.colors_crit.bg = "#ffaaaa",
|
||||||
.colors_crit.fg = "#000000",
|
.colors_crit.fg = "#000000",
|
||||||
|
.colors_crit.highlight = "#ff6666",
|
||||||
.colors_low.bg = "#aaaaff",
|
.colors_low.bg = "#aaaaff",
|
||||||
.colors_low.fg = "#000000",
|
.colors_low.fg = "#000000",
|
||||||
|
.colors_low.highlight = "#7f7fff",
|
||||||
.format = "%s %b", /* default format */
|
.format = "%s %b", /* default format */
|
||||||
|
|
||||||
.timeouts = { S2US(10), S2US(10), S2US(0) }, /* low, normal, critical */
|
.timeouts = { S2US(10), S2US(10), S2US(0) }, /* low, normal, critical */
|
||||||
@ -110,6 +113,15 @@ struct settings defaults = {
|
|||||||
|
|
||||||
.mouse_right_click = (enum mouse_action []){MOUSE_CLOSE_ALL, -1},
|
.mouse_right_click = (enum mouse_action []){MOUSE_CLOSE_ALL, -1},
|
||||||
|
|
||||||
|
.progress_bar_height = 10,
|
||||||
|
|
||||||
|
.progress_bar_min_width = 150,
|
||||||
|
|
||||||
|
.progress_bar_max_width = 300,
|
||||||
|
|
||||||
|
.progress_bar_frame_width = 1,
|
||||||
|
|
||||||
|
.no_progress_bar = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct rule default_rules[] = {
|
struct rule default_rules[] = {
|
||||||
|
72
src/draw.c
72
src/draw.c
@ -23,6 +23,7 @@ struct colored_layout {
|
|||||||
PangoLayout *l;
|
PangoLayout *l;
|
||||||
struct color fg;
|
struct color fg;
|
||||||
struct color bg;
|
struct color bg;
|
||||||
|
struct color highlight;
|
||||||
struct color frame;
|
struct color frame;
|
||||||
char *text;
|
char *text;
|
||||||
PangoAttrList *attr;
|
PangoAttrList *attr;
|
||||||
@ -166,6 +167,11 @@ static bool have_dynamic_width(void)
|
|||||||
return (settings.geometry.width_set && settings.geometry.w == 0);
|
return (settings.geometry.width_set && settings.geometry.w == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool have_progress_bar(const struct notification *n)
|
||||||
|
{
|
||||||
|
return (n->progress >= 0 && settings.no_progress_bar == false);
|
||||||
|
}
|
||||||
|
|
||||||
static struct dimensions calculate_dimensions(GSList *layouts)
|
static struct dimensions calculate_dimensions(GSList *layouts)
|
||||||
{
|
{
|
||||||
struct dimensions dim = { 0 };
|
struct dimensions dim = { 0 };
|
||||||
@ -237,6 +243,11 @@ static struct dimensions calculate_dimensions(GSList *layouts)
|
|||||||
text_width = MAX(w, text_width);
|
text_width = MAX(w, text_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (have_progress_bar(cl->n)){
|
||||||
|
dim.h += settings.progress_bar_height + settings.padding;
|
||||||
|
dim.w = MAX(dim.w, settings.progress_bar_min_width);
|
||||||
|
}
|
||||||
|
|
||||||
dim.corner_radius = MIN(dim.corner_radius, h/2);
|
dim.corner_radius = MIN(dim.corner_radius, h/2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,6 +310,7 @@ static struct colored_layout *layout_init_shared(cairo_t *c, const struct notifi
|
|||||||
|
|
||||||
cl->fg = string_to_color(n->colors.fg);
|
cl->fg = string_to_color(n->colors.fg);
|
||||||
cl->bg = string_to_color(n->colors.bg);
|
cl->bg = string_to_color(n->colors.bg);
|
||||||
|
cl->highlight = string_to_color(n->colors.highlight);
|
||||||
cl->frame = string_to_color(n->colors.frame);
|
cl->frame = string_to_color(n->colors.frame);
|
||||||
|
|
||||||
cl->n = n;
|
cl->n = n;
|
||||||
@ -354,7 +366,12 @@ static struct colored_layout *layout_from_notification(cairo_t *c, struct notifi
|
|||||||
|
|
||||||
pango_layout_get_pixel_size(cl->l, NULL, &(n->displayed_height));
|
pango_layout_get_pixel_size(cl->l, NULL, &(n->displayed_height));
|
||||||
if (cl->icon) n->displayed_height = MAX(cairo_image_surface_get_height(cl->icon), n->displayed_height);
|
if (cl->icon) n->displayed_height = MAX(cairo_image_surface_get_height(cl->icon), n->displayed_height);
|
||||||
n->displayed_height = MAX(settings.notification_height, n->displayed_height + settings.padding * 2);
|
n->displayed_height = n->displayed_height + settings.padding * 2;
|
||||||
|
|
||||||
|
// progress bar
|
||||||
|
if (have_progress_bar(n)) n->displayed_height += settings.progress_bar_height + settings.padding;
|
||||||
|
|
||||||
|
n->displayed_height = MAX(settings.notification_height, n->displayed_height);
|
||||||
|
|
||||||
n->first_render = false;
|
n->first_render = false;
|
||||||
return cl;
|
return cl;
|
||||||
@ -397,11 +414,16 @@ static int layout_get_height(struct colored_layout *cl)
|
|||||||
{
|
{
|
||||||
int h;
|
int h;
|
||||||
int h_icon = 0;
|
int h_icon = 0;
|
||||||
|
int h_progress_bar = 0;
|
||||||
pango_layout_get_pixel_size(cl->l, NULL, &h);
|
pango_layout_get_pixel_size(cl->l, NULL, &h);
|
||||||
if (cl->icon)
|
if (cl->icon)
|
||||||
h_icon = cairo_image_surface_get_height(cl->icon);
|
h_icon = cairo_image_surface_get_height(cl->icon);
|
||||||
|
if (have_progress_bar(cl->n)){
|
||||||
|
h_progress_bar = settings.progress_bar_height + settings.padding;
|
||||||
|
}
|
||||||
|
|
||||||
return MAX(h, h_icon);
|
int res = MAX(h, h_icon) + h_progress_bar;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Attempt to make internal radius more organic.
|
/* Attempt to make internal radius more organic.
|
||||||
@ -563,11 +585,15 @@ static cairo_surface_t *render_background(cairo_surface_t *srf,
|
|||||||
static void render_content(cairo_t *c, struct colored_layout *cl, int width)
|
static void render_content(cairo_t *c, struct colored_layout *cl, int width)
|
||||||
{
|
{
|
||||||
const int h = layout_get_height(cl);
|
const int h = layout_get_height(cl);
|
||||||
|
int h_without_progress_bar = h;
|
||||||
|
if (have_progress_bar(cl->n)){
|
||||||
|
h_without_progress_bar -= settings.progress_bar_height + settings.padding;
|
||||||
|
}
|
||||||
int h_text;
|
int h_text;
|
||||||
pango_layout_get_pixel_size(cl->l, NULL, &h_text);
|
pango_layout_get_pixel_size(cl->l, NULL, &h_text);
|
||||||
|
|
||||||
int text_x = settings.h_padding,
|
int text_x = settings.h_padding,
|
||||||
text_y = settings.padding + h / 2 - h_text / 2;
|
text_y = settings.padding + h_without_progress_bar / 2 - h_text / 2;
|
||||||
|
|
||||||
// text positioning
|
// text positioning
|
||||||
if (cl->icon) {
|
if (cl->icon) {
|
||||||
@ -575,7 +601,7 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width)
|
|||||||
if (settings.vertical_alignment == VERTICAL_TOP) {
|
if (settings.vertical_alignment == VERTICAL_TOP) {
|
||||||
text_y = settings.padding;
|
text_y = settings.padding;
|
||||||
} else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
|
} else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
|
||||||
text_y = h + settings.padding - h_text;
|
text_y = h_without_progress_bar + settings.padding - h_text;
|
||||||
if (text_y < 0)
|
if (text_y < 0)
|
||||||
text_y = settings.padding;
|
text_y = settings.padding;
|
||||||
} // else VERTICAL_CENTER
|
} // else VERTICAL_CENTER
|
||||||
@ -597,14 +623,14 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width)
|
|||||||
unsigned int image_width = cairo_image_surface_get_width(cl->icon),
|
unsigned int image_width = cairo_image_surface_get_width(cl->icon),
|
||||||
image_height = cairo_image_surface_get_height(cl->icon),
|
image_height = cairo_image_surface_get_height(cl->icon),
|
||||||
image_x = width - settings.h_padding - image_width,
|
image_x = width - settings.h_padding - image_width,
|
||||||
image_y = settings.padding + h/2 - image_height/2;
|
image_y = settings.padding + h_without_progress_bar/2 - image_height/2;
|
||||||
|
|
||||||
// vertical alignment
|
// vertical alignment
|
||||||
if (settings.vertical_alignment == VERTICAL_TOP) {
|
if (settings.vertical_alignment == VERTICAL_TOP) {
|
||||||
image_y = settings.padding;
|
image_y = settings.padding;
|
||||||
} else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
|
} else if (settings.vertical_alignment == VERTICAL_BOTTOM) {
|
||||||
image_y = h + settings.padding - image_height;
|
image_y = h_without_progress_bar + settings.padding - image_height;
|
||||||
if (image_y < settings.padding || image_y > h)
|
if (image_y < settings.padding || image_y > h_without_progress_bar)
|
||||||
image_y = settings.padding;
|
image_y = settings.padding;
|
||||||
} // else VERTICAL_CENTER
|
} // else VERTICAL_CENTER
|
||||||
|
|
||||||
@ -618,6 +644,38 @@ static void render_content(cairo_t *c, struct colored_layout *cl, int width)
|
|||||||
cairo_fill(c);
|
cairo_fill(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// progress bar positioning
|
||||||
|
if (have_progress_bar(cl->n)){
|
||||||
|
int progress = cl->n->progress;
|
||||||
|
unsigned int frame_width = settings.progress_bar_frame_width,
|
||||||
|
progress_width = MIN(width - 2 * settings.h_padding, settings.progress_bar_max_width),
|
||||||
|
progress_height = settings.progress_bar_height - frame_width,
|
||||||
|
frame_x = settings.h_padding,
|
||||||
|
frame_y = settings.padding + h - settings.progress_bar_height,
|
||||||
|
progress_width_without_frame = progress_width - 2 * frame_width,
|
||||||
|
progress_width_1 = progress_width_without_frame * progress / 100,
|
||||||
|
progress_width_2 = progress_width_without_frame - progress_width_1,
|
||||||
|
x_bar_1 = frame_x + frame_width,
|
||||||
|
x_bar_2 = x_bar_1 + progress_width_1;
|
||||||
|
|
||||||
|
double half_frame_width = frame_width / 2.0;
|
||||||
|
|
||||||
|
// draw progress bar
|
||||||
|
// Note: the bar could be drawn a bit smaller, because the frame is drawn on top
|
||||||
|
// left side
|
||||||
|
cairo_set_source_rgba(c, cl->highlight.r, cl->highlight.g, cl->highlight.b, cl->highlight.a);
|
||||||
|
cairo_rectangle(c, x_bar_1, frame_y, progress_width_1, progress_height);
|
||||||
|
cairo_fill(c);
|
||||||
|
// right side
|
||||||
|
cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a);
|
||||||
|
cairo_rectangle(c, x_bar_2, frame_y, progress_width_2, progress_height);
|
||||||
|
cairo_fill(c);
|
||||||
|
// border
|
||||||
|
cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a);
|
||||||
|
cairo_rectangle(c, frame_x + half_frame_width, frame_y + half_frame_width, progress_width - frame_width, progress_height);
|
||||||
|
cairo_set_line_width(c, frame_width);
|
||||||
|
cairo_stroke(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct dimensions layout_render(cairo_surface_t *srf,
|
static struct dimensions layout_render(cairo_surface_t *srf,
|
||||||
|
@ -64,6 +64,7 @@ void notification_print(const struct notification *n)
|
|||||||
printf("\tformatted: '%s'\n", n->msg);
|
printf("\tformatted: '%s'\n", n->msg);
|
||||||
printf("\tfg: %s\n", n->colors.fg);
|
printf("\tfg: %s\n", n->colors.fg);
|
||||||
printf("\tbg: %s\n", n->colors.bg);
|
printf("\tbg: %s\n", n->colors.bg);
|
||||||
|
printf("\thighlight: %s\n", n->colors.highlight);
|
||||||
printf("\tframe: %s\n", n->colors.frame);
|
printf("\tframe: %s\n", n->colors.frame);
|
||||||
printf("\tfullscreen: %s\n", enum_to_string_fullscreen(n->fullscreen));
|
printf("\tfullscreen: %s\n", enum_to_string_fullscreen(n->fullscreen));
|
||||||
printf("\tprogress: %d\n", n->progress);
|
printf("\tprogress: %d\n", n->progress);
|
||||||
@ -236,6 +237,7 @@ void notification_unref(struct notification *n)
|
|||||||
g_free(n->urls);
|
g_free(n->urls);
|
||||||
g_free(n->colors.fg);
|
g_free(n->colors.fg);
|
||||||
g_free(n->colors.bg);
|
g_free(n->colors.bg);
|
||||||
|
g_free(n->colors.highlight);
|
||||||
g_free(n->colors.frame);
|
g_free(n->colors.frame);
|
||||||
g_free(n->stack_tag);
|
g_free(n->stack_tag);
|
||||||
g_free(n->desktop_entry);
|
g_free(n->desktop_entry);
|
||||||
@ -393,6 +395,8 @@ void notification_init(struct notification *n)
|
|||||||
n->colors.fg = g_strdup(defcolors.fg);
|
n->colors.fg = g_strdup(defcolors.fg);
|
||||||
if (!n->colors.bg)
|
if (!n->colors.bg)
|
||||||
n->colors.bg = g_strdup(defcolors.bg);
|
n->colors.bg = g_strdup(defcolors.bg);
|
||||||
|
if (!n->colors.highlight)
|
||||||
|
n->colors.highlight = g_strdup(defcolors.highlight);
|
||||||
if (!n->colors.frame)
|
if (!n->colors.frame)
|
||||||
n->colors.frame = g_strdup(defcolors.frame);
|
n->colors.frame = g_strdup(defcolors.frame);
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ struct notification_colors {
|
|||||||
char *frame;
|
char *frame;
|
||||||
char *bg;
|
char *bg;
|
||||||
char *fg;
|
char *fg;
|
||||||
|
char *highlight;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct notification {
|
struct notification {
|
||||||
|
@ -38,6 +38,10 @@ void rule_apply(struct rule *r, struct notification *n)
|
|||||||
g_free(n->colors.bg);
|
g_free(n->colors.bg);
|
||||||
n->colors.bg = g_strdup(r->bg);
|
n->colors.bg = g_strdup(r->bg);
|
||||||
}
|
}
|
||||||
|
if (r->highlight) {
|
||||||
|
g_free(n->colors.highlight);
|
||||||
|
n->colors.highlight = g_strdup(r->highlight);
|
||||||
|
}
|
||||||
if (r->fc) {
|
if (r->fc) {
|
||||||
g_free(n->colors.frame);
|
g_free(n->colors.frame);
|
||||||
n->colors.frame = g_strdup(r->fc);
|
n->colors.frame = g_strdup(r->fc);
|
||||||
|
@ -31,6 +31,7 @@ struct rule {
|
|||||||
char *new_icon;
|
char *new_icon;
|
||||||
char *fg;
|
char *fg;
|
||||||
char *bg;
|
char *bg;
|
||||||
|
char *highlight;
|
||||||
char *fc;
|
char *fc;
|
||||||
const char *format;
|
const char *format;
|
||||||
const char *script;
|
const char *script;
|
||||||
|
@ -355,6 +355,49 @@ void load_settings(char *cmdline_config_path)
|
|||||||
"Window corner radius"
|
"Window corner radius"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
settings.progress_bar_height = option_get_int(
|
||||||
|
"global",
|
||||||
|
"progress_bar_height", "-progress_bar_height", defaults.progress_bar_height,
|
||||||
|
"Height of the progress bar"
|
||||||
|
);
|
||||||
|
|
||||||
|
settings.progress_bar_min_width = option_get_int(
|
||||||
|
"global",
|
||||||
|
"progress_bar_min_width", "-progress_bar_min_width", defaults.progress_bar_min_width,
|
||||||
|
"Minimum width of the progress bar"
|
||||||
|
);
|
||||||
|
|
||||||
|
settings.progress_bar_max_width = option_get_int(
|
||||||
|
"global",
|
||||||
|
"progress_bar_max_width", "-progress_bar_max_width", defaults.progress_bar_max_width,
|
||||||
|
"Maximum width of the progress bar"
|
||||||
|
);
|
||||||
|
|
||||||
|
settings.progress_bar_frame_width = option_get_int(
|
||||||
|
"global",
|
||||||
|
"progress_bar_frame_width", "-progress_bar_frame_width", defaults.progress_bar_frame_width,
|
||||||
|
"Frame width of the progress bar"
|
||||||
|
);
|
||||||
|
|
||||||
|
settings.no_progress_bar = option_get_bool(
|
||||||
|
"global",
|
||||||
|
"no_progress_bar", "-no_progress_bar", false,
|
||||||
|
"Hide the progress bar"
|
||||||
|
);
|
||||||
|
|
||||||
|
// check sanity of the progress bar options
|
||||||
|
{
|
||||||
|
if (settings.progress_bar_height < (2 * settings.progress_bar_frame_width)){
|
||||||
|
LOG_E("setting progress_bar_frame_width is bigger than half of progress_bar_height");
|
||||||
|
}
|
||||||
|
if (settings.progress_bar_max_width < (2 * settings.progress_bar_frame_width)){
|
||||||
|
LOG_E("setting progress_bar_frame_width is bigger than half of progress_bar_max_width");
|
||||||
|
}
|
||||||
|
if (settings.progress_bar_max_width < settings.progress_bar_min_width){
|
||||||
|
LOG_E("setting progress_bar_max_width is smaller than progress_bar_min_width");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
char *c = option_get_string(
|
char *c = option_get_string(
|
||||||
"global",
|
"global",
|
||||||
@ -584,6 +627,12 @@ void load_settings(char *cmdline_config_path)
|
|||||||
"Foreground color for notifications with low urgency"
|
"Foreground color for notifications with low urgency"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
settings.colors_low.highlight = option_get_string(
|
||||||
|
"urgency_low",
|
||||||
|
"highlight", "-lh", defaults.colors_low.highlight,
|
||||||
|
"Highlight color for notifications with low urgency"
|
||||||
|
);
|
||||||
|
|
||||||
settings.colors_low.frame = option_get_string(
|
settings.colors_low.frame = option_get_string(
|
||||||
"urgency_low",
|
"urgency_low",
|
||||||
"frame_color", "-lfr", settings.frame_color ? settings.frame_color : defaults.colors_low.frame,
|
"frame_color", "-lfr", settings.frame_color ? settings.frame_color : defaults.colors_low.frame,
|
||||||
@ -614,6 +663,12 @@ void load_settings(char *cmdline_config_path)
|
|||||||
"Foreground color for notifications with normal urgency"
|
"Foreground color for notifications with normal urgency"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
settings.colors_norm.highlight = option_get_string(
|
||||||
|
"urgency_normal",
|
||||||
|
"highlight", "-nh", defaults.colors_norm.highlight,
|
||||||
|
"Highlight color for notifications with normal urgency"
|
||||||
|
);
|
||||||
|
|
||||||
settings.colors_norm.frame = option_get_string(
|
settings.colors_norm.frame = option_get_string(
|
||||||
"urgency_normal",
|
"urgency_normal",
|
||||||
"frame_color", "-nfr", settings.frame_color ? settings.frame_color : defaults.colors_norm.frame,
|
"frame_color", "-nfr", settings.frame_color ? settings.frame_color : defaults.colors_norm.frame,
|
||||||
@ -644,6 +699,12 @@ void load_settings(char *cmdline_config_path)
|
|||||||
"Foreground color for notifications with ciritical urgency"
|
"Foreground color for notifications with ciritical urgency"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
settings.colors_crit.highlight = option_get_string(
|
||||||
|
"urgency_critical",
|
||||||
|
"highlight", "-ch", defaults.colors_crit.highlight,
|
||||||
|
"Highlight color for notifications with ciritical urgency"
|
||||||
|
);
|
||||||
|
|
||||||
settings.colors_crit.frame = option_get_string(
|
settings.colors_crit.frame = option_get_string(
|
||||||
"urgency_critical",
|
"urgency_critical",
|
||||||
"frame_color", "-cfr", settings.frame_color ? settings.frame_color : defaults.colors_crit.frame,
|
"frame_color", "-cfr", settings.frame_color ? settings.frame_color : defaults.colors_crit.frame,
|
||||||
@ -756,6 +817,7 @@ void load_settings(char *cmdline_config_path)
|
|||||||
r->msg_urgency = ini_get_urgency(cur_section, "msg_urgency", r->msg_urgency);
|
r->msg_urgency = ini_get_urgency(cur_section, "msg_urgency", r->msg_urgency);
|
||||||
r->fg = ini_get_string(cur_section, "foreground", r->fg);
|
r->fg = ini_get_string(cur_section, "foreground", r->fg);
|
||||||
r->bg = ini_get_string(cur_section, "background", r->bg);
|
r->bg = ini_get_string(cur_section, "background", r->bg);
|
||||||
|
r->bg = ini_get_string(cur_section, "highlight", r->highlight);
|
||||||
r->fc = ini_get_string(cur_section, "frame_color", r->fc);
|
r->fc = ini_get_string(cur_section, "frame_color", r->fc);
|
||||||
r->format = ini_get_string(cur_section, "format", r->format);
|
r->format = ini_get_string(cur_section, "format", r->format);
|
||||||
r->new_icon = ini_get_string(cur_section, "new_icon", r->new_icon);
|
r->new_icon = ini_get_string(cur_section, "new_icon", r->new_icon);
|
||||||
|
@ -92,6 +92,11 @@ struct settings {
|
|||||||
enum mouse_action *mouse_left_click;
|
enum mouse_action *mouse_left_click;
|
||||||
enum mouse_action *mouse_middle_click;
|
enum mouse_action *mouse_middle_click;
|
||||||
enum mouse_action *mouse_right_click;
|
enum mouse_action *mouse_right_click;
|
||||||
|
int progress_bar_height;
|
||||||
|
int progress_bar_min_width;
|
||||||
|
int progress_bar_max_width;
|
||||||
|
int progress_bar_frame_width;
|
||||||
|
bool no_progress_bar;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct settings settings;
|
extern struct settings settings;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user