513 lines
16 KiB
C
513 lines
16 KiB
C
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <wayland-client-protocol.h>
|
|
|
|
#include <pango-1.0/pango/pangocairo.h>
|
|
|
|
#include "config.h"
|
|
#include "bar.h"
|
|
#include "common.h"
|
|
#include "pango/pango-font.h"
|
|
#include "pango/pango-layout.h"
|
|
#include "shm.h"
|
|
#include "wlr-layer-shell-unstable-v1-protocol.h"
|
|
#include "xdg-shell-protocol.h"
|
|
|
|
#define ELIPSES 3
|
|
|
|
typedef struct Font {
|
|
PangoFontDescription* description;
|
|
|
|
uint height; /* This is also the same as lrpad from dwm. */
|
|
uint approx_width;
|
|
} Font;
|
|
|
|
typedef struct BarComponent {
|
|
PangoLayout* layout;
|
|
int x; /* Right bound of box */
|
|
} BarComponent;
|
|
|
|
typedef struct {
|
|
uint occupied;
|
|
uint focusedClient; /* If the tag has a focused client */
|
|
uint state;
|
|
BarComponent component;
|
|
} Tag;
|
|
|
|
struct Bar {
|
|
BarComponent layout, title, status;
|
|
Tag tags[9];
|
|
|
|
PangoContext* context;
|
|
|
|
/* Colors */
|
|
int background[4], foreground[4];
|
|
|
|
uint invalid; /* So we don't redraw twice. */
|
|
uint active; /* If this bar is on the active monitor */
|
|
uint floating; /* If the focused client is floating */
|
|
|
|
wl_surface* surface;
|
|
zwlr_layer_surface_v1* layer_surface;
|
|
|
|
Shm* shm;
|
|
};
|
|
|
|
static void add_elipses(PangoLayout *layout, int current_size);
|
|
static void layerSurface(void* data, zwlr_layer_surface_v1*, uint32_t serial, uint32_t width, uint32_t height);
|
|
static void frame(void* data, wl_callback* callback, uint32_t callback_data);
|
|
static void bar_render(Bar* bar);
|
|
static void bar_tags_render(Bar* bar, cairo_t* painter, int* x);
|
|
static void bar_layout_render(Bar* bar, cairo_t* painter, int* x);
|
|
static void bar_title_render(Bar* bar, cairo_t* painter, int* x);
|
|
static void bar_status_render(Bar* bar, cairo_t* painter, int* x);
|
|
static void bar_set_colorscheme(Bar* bar, const int** scheme);
|
|
static void set_color(cairo_t* painter, const int rgba[4]);
|
|
static void bar_color_background(Bar* bar, cairo_t* painter);
|
|
static void bar_color_foreground(Bar* bar, cairo_t* painter);
|
|
static Font getFont(void);
|
|
static BarComponent bar_component_create(PangoContext* context, PangoFontDescription* description);
|
|
static void bar_component_render(Bar* bar, BarComponent* component, cairo_t* painter, uint width, int* x);
|
|
static int bar_component_width(BarComponent* component);
|
|
|
|
static Font bar_font = {NULL, 0};
|
|
|
|
// So that the compositor can tell us when it's a good time to render again.
|
|
const wl_callback_listener frameListener = {.done = frame};
|
|
|
|
// So that wlroots can tell us we need to resize.
|
|
// We really only need to worry about this when the bar is visible (sometimes it isn't).
|
|
const zwlr_layer_surface_v1_listener layerSurfaceListener = {.configure = layerSurface};
|
|
|
|
void layerSurface(void* data, zwlr_layer_surface_v1* _, uint32_t serial, uint32_t width, uint32_t height) {
|
|
Bar* bar = data;
|
|
zwlr_layer_surface_v1_ack_configure(bar->layer_surface, serial);
|
|
|
|
if (bar->shm) {
|
|
if (bar->shm->width == width && bar->shm->height)
|
|
return;
|
|
shm_destroy(bar->shm);
|
|
}
|
|
|
|
bar->shm = shm_create(width, height, WL_SHM_FORMAT_XRGB8888);
|
|
bar_render(bar);
|
|
}
|
|
|
|
void frame(void* data, wl_callback* callback, uint32_t callback_data) {
|
|
Bar* bar = data;
|
|
bar_render(bar);
|
|
wl_callback_destroy(callback);
|
|
}
|
|
|
|
Font getFont(void) {
|
|
PangoFontMap* map = pango_cairo_font_map_get_default();
|
|
if (!map)
|
|
die("font map");
|
|
|
|
PangoFontDescription* desc = pango_font_description_from_string(font);
|
|
if (!desc)
|
|
die("font description");
|
|
|
|
PangoContext* context = pango_font_map_create_context(map);
|
|
if (!context)
|
|
die("temp context");
|
|
|
|
PangoFont* fnt = pango_font_map_load_font(map, context, desc);
|
|
if (!fnt)
|
|
die("font load");
|
|
|
|
PangoFontMetrics* metrics = pango_font_get_metrics(fnt, pango_language_get_default());
|
|
if (!metrics)
|
|
die("font metrics");
|
|
|
|
Font in = {
|
|
desc,
|
|
PANGO_PIXELS(pango_font_metrics_get_height(metrics)),
|
|
PANGO_PIXELS(pango_font_metrics_get_approximate_char_width(metrics))
|
|
};
|
|
|
|
pango_font_metrics_unref(metrics);
|
|
g_object_unref(fnt);
|
|
g_object_unref(context);
|
|
|
|
return in;
|
|
}
|
|
|
|
void add_elipses(PangoLayout *layout, int current_size) {
|
|
const char *str = pango_layout_get_text(layout);
|
|
char *new_str;
|
|
int i = 0;
|
|
|
|
for (i = strlen(str); (((i+ELIPSES)*bar_font.approx_width)+bar_font.height > current_size
|
|
&& i >= 0); i--);
|
|
|
|
if (i <= 0) {
|
|
pango_layout_set_text(layout, "", -1);
|
|
} else {
|
|
new_str = ecalloc(i+ELIPSES+1, sizeof(char));
|
|
new_str = strncpy(new_str, str, i*sizeof(char));
|
|
new_str[i+1] = '\0';
|
|
new_str = strcat(new_str, "...");
|
|
|
|
pango_layout_set_text(layout, new_str, -1);
|
|
free(new_str);
|
|
}
|
|
}
|
|
|
|
BarComponent bar_component_create(PangoContext* context, PangoFontDescription* description) {
|
|
PangoLayout* layout = pango_layout_new(context);
|
|
pango_layout_set_font_description(layout, description);
|
|
|
|
return (BarComponent){ layout, 0 };
|
|
}
|
|
|
|
int bar_component_width(BarComponent* component) {
|
|
int w;
|
|
pango_layout_get_size(component->layout, &w, NULL);
|
|
return PANGO_PIXELS(w);
|
|
}
|
|
|
|
void bar_set_colorscheme(Bar* bar, const int** scheme) {
|
|
for (int i = 0; i < 4; i++) {
|
|
bar->foreground[i] = scheme[0][i];
|
|
bar->background[i] = scheme[1][i];
|
|
}
|
|
}
|
|
|
|
void set_color(cairo_t* painter, const int rgba[4]) {
|
|
cairo_set_source_rgba(painter, rgba[0]/255.0, rgba[1]/255.0, rgba[2]/255.0, rgba[3]/255.0);
|
|
}
|
|
|
|
void bar_color_background(Bar* bar, cairo_t* painter) {
|
|
set_color(painter, bar->background);
|
|
}
|
|
|
|
void bar_color_foreground(Bar* bar, cairo_t* painter) {
|
|
set_color(painter, bar->foreground);
|
|
}
|
|
|
|
void bar_component_render(Bar* bar, BarComponent* component, cairo_t* painter, uint width, int* x) {
|
|
pango_cairo_update_layout(painter, component->layout);
|
|
component->x = *x+width;
|
|
|
|
bar_color_background(bar, painter);
|
|
cairo_rectangle(painter, *x, 0, width, bar->shm->height);
|
|
cairo_fill(painter);
|
|
|
|
bar_color_foreground(bar, painter);
|
|
cairo_move_to(painter, *x+(bar_font.height/2.0), 1);
|
|
pango_cairo_show_layout(painter, component->layout);
|
|
}
|
|
|
|
void bar_tags_render(Bar* bar, cairo_t* painter, int* x) {
|
|
for ( int i = 0; i < LENGTH(bar->tags); i++ ){
|
|
Tag* tag = &bar->tags[i];
|
|
uint tagWidth = bar_component_width(&tag->component) + bar_font.height;
|
|
|
|
/* Creating the tag */
|
|
if (tag->state & TAG_ACTIVE) {
|
|
bar_set_colorscheme(bar, schemes[Active_Scheme]);
|
|
} else if (tag->state & TAG_URGENT) {
|
|
bar_set_colorscheme(bar, schemes[Urgent_Scheme]);
|
|
} else {
|
|
bar_set_colorscheme(bar, schemes[InActive_Scheme]);
|
|
}
|
|
|
|
bar_component_render(bar, &tag->component, painter, tagWidth, x);
|
|
|
|
if (!tag->occupied)
|
|
goto done;
|
|
|
|
/* Creating the occupied tag box */
|
|
int boxHeight = bar_font.height / 9,
|
|
boxWidth = bar_font.height / 6 + 1;
|
|
|
|
if (tag->focusedClient) {
|
|
cairo_rectangle(painter, *x + boxHeight, boxHeight, boxWidth, boxWidth);
|
|
cairo_fill(painter);
|
|
} else {
|
|
cairo_rectangle(painter, *x + boxHeight + 0.5, boxHeight + 0.5, boxWidth, boxWidth);
|
|
cairo_set_line_width(painter, 1);
|
|
cairo_stroke(painter);
|
|
}
|
|
|
|
done:
|
|
*x += tagWidth;
|
|
}
|
|
}
|
|
|
|
void bar_layout_render(Bar* bar, cairo_t* painter, int* x) {
|
|
if (!bar)
|
|
return;
|
|
|
|
uint layoutWidth = bar_component_width(&bar->layout) + bar_font.height;
|
|
|
|
bar_set_colorscheme(bar, schemes[InActive_Scheme]);
|
|
bar_component_render(bar, &bar->layout, painter, layoutWidth, x);
|
|
|
|
*x += layoutWidth;
|
|
}
|
|
|
|
void bar_title_render(Bar* bar, cairo_t* painter, int* x) {
|
|
if (!bar)
|
|
return;
|
|
|
|
// @HUH: For some reason ww - x - (status width) works, but ww - x - status width doesn't?
|
|
int titleWidth = bar->shm->width - bar->layout.x - (bar_component_width(&bar->status) + bar_font.height);
|
|
|
|
/* If the status is larger than the title
|
|
* or
|
|
* a character can't fit in the title.
|
|
* Hopefully this helps avoid situations where the title is empty
|
|
* and renders but wouldn't if it had text.
|
|
*/
|
|
if (titleWidth < 0 || bar_font.approx_width+bar_font.height > titleWidth)
|
|
return;
|
|
|
|
/* If not all text fills the title component
|
|
* Then fit as much as possible.
|
|
*/
|
|
if ((bar_component_width(&bar->title) + bar_font.height) > titleWidth)
|
|
add_elipses(bar->title.layout, titleWidth);
|
|
|
|
bar->active ? bar_set_colorscheme(bar, schemes[Active_Scheme]) : bar_set_colorscheme(bar, schemes[InActive_Scheme]);
|
|
|
|
bar_component_render(bar, &bar->title, painter, titleWidth, x);
|
|
|
|
if (!bar->floating)
|
|
goto done;
|
|
|
|
int boxHeight = bar_font.height / 9,
|
|
boxWidth = bar_font.height / 6 + 1;
|
|
|
|
set_color(painter, grey3);
|
|
cairo_rectangle(painter, *x + boxHeight + 0.5, boxHeight + 0.5, boxWidth, boxWidth);
|
|
cairo_set_line_width(painter, 1);
|
|
cairo_stroke(painter);
|
|
|
|
done:
|
|
*x += titleWidth;
|
|
}
|
|
|
|
void bar_status_render(Bar* bar, cairo_t* painter, int* x) {
|
|
if (!bar)
|
|
return;
|
|
|
|
uint statusWidth = bar_component_width(&bar->status) + bar_font.height;
|
|
|
|
// If the status is as large or larger than the layout then fit as much as we can.
|
|
if (statusWidth > (bar->shm->width - bar->layout.x))
|
|
add_elipses(bar->status.layout, (bar->shm->width - bar->layout.x));
|
|
|
|
bar_set_colorscheme(bar, schemes[InActive_Scheme]);
|
|
if (!bar->active && status_on_active)
|
|
bar_set_colorscheme(bar, (const int*[4]){ grey1, grey1 } );
|
|
|
|
bar_component_render(bar, &bar->status, painter, statusWidth, x);
|
|
}
|
|
|
|
void bar_render(Bar* bar) {
|
|
if (!bar || !bar->shm)
|
|
return;
|
|
|
|
int x = 0; /* Keep track of the cairo cursor */
|
|
cairo_surface_t* image = cairo_image_surface_create_for_data(shm_data(bar->shm),
|
|
CAIRO_FORMAT_ARGB32,
|
|
bar->shm->width,
|
|
bar->shm->height,
|
|
bar->shm->stride);
|
|
|
|
cairo_t* painter = cairo_create(image);
|
|
pango_cairo_update_context(painter, bar->context);
|
|
|
|
bar_tags_render(bar, painter, &x);
|
|
bar_layout_render(bar, painter, &x);
|
|
bar_title_render(bar, painter, &x);
|
|
bar_status_render(bar, painter, &x);
|
|
|
|
wl_surface_attach(bar->surface, shm_buffer(bar->shm), 0, 0);
|
|
wl_surface_damage(bar->surface, 0, 0, bar->shm->width, bar->shm->height);
|
|
wl_surface_commit(bar->surface);
|
|
|
|
cairo_destroy(painter);
|
|
cairo_surface_destroy(image);
|
|
|
|
shm_flip(bar->shm);
|
|
bar->invalid = 0;
|
|
}
|
|
|
|
Bar* bar_create(void) {
|
|
Bar* bar = ecalloc(1, sizeof(*bar));
|
|
bar->invalid = 0;
|
|
bar->active = 0;
|
|
bar->floating = 0;
|
|
|
|
bar->context = pango_font_map_create_context(pango_cairo_font_map_get_default());
|
|
if (!bar->context)
|
|
die("pango context");
|
|
|
|
if (!bar_font.description)
|
|
bar_font = getFont();
|
|
|
|
bar->layout = bar_component_create(bar->context, bar_font.description);
|
|
bar->title = bar_component_create(bar->context, bar_font.description);
|
|
bar->status = bar_component_create(bar->context, bar_font.description);
|
|
|
|
/* Default status */
|
|
char* status = ecalloc(8, sizeof(*status));
|
|
snprintf(status, 8, "dwl %.1f", VERSION);
|
|
pango_layout_set_text(bar->status.layout, status, strlen(status));
|
|
free(status);
|
|
|
|
for (int i = 0; i < LENGTH(tags); i++) {
|
|
BarComponent component = bar_component_create(bar->context, bar_font.description);
|
|
pango_layout_set_text(component.layout, tags[i], strlen(tags[i]));
|
|
Tag tag = { 0, 0, 0, component };
|
|
bar->tags[i] = tag;
|
|
}
|
|
|
|
return bar;
|
|
}
|
|
|
|
void bar_destroy(Bar* bar) {
|
|
uint i;
|
|
if ( !bar )
|
|
return;
|
|
|
|
if ( bar->shm )
|
|
shm_destroy(bar->shm);
|
|
|
|
if ( bar->surface )
|
|
wl_surface_destroy(bar->surface);
|
|
|
|
if ( bar->layer_surface )
|
|
zwlr_layer_surface_v1_destroy(bar->layer_surface);
|
|
|
|
if ( bar->context )
|
|
g_object_unref(bar->context);
|
|
|
|
if ( bar->layout.layout )
|
|
g_object_unref(bar->layout.layout);
|
|
|
|
if ( bar->status.layout )
|
|
g_object_unref(bar->status.layout);
|
|
|
|
if ( bar->title.layout )
|
|
g_object_unref(bar->title.layout);
|
|
|
|
for ( i = 0; i < LENGTH(tags); i++) {
|
|
Tag tag = bar->tags[i];
|
|
if (tag.component.layout)
|
|
g_object_unref(tag.component.layout);
|
|
}
|
|
|
|
return free(bar);
|
|
}
|
|
|
|
// When we need to redraw the bar, because of new information or changes.
|
|
// We don't just redraw the bar immediately, we will wait for the compositor to say it's ready.
|
|
// This is only for if the bar is shown
|
|
void bar_invalidate(Bar* bar) {
|
|
if ( !bar || bar->invalid || !bar_is_visible(bar))
|
|
return;
|
|
|
|
wl_callback* cb = wl_surface_frame(bar->surface);
|
|
|
|
wl_callback_add_listener(cb, &frameListener, bar);
|
|
wl_surface_commit(bar->surface);
|
|
bar->invalid = 1;
|
|
}
|
|
|
|
void bar_show(Bar* bar, wl_output* output) {
|
|
if (!bar || !output || bar_is_visible(bar))
|
|
return;
|
|
|
|
bar->surface = wl_compositor_create_surface(compositor);
|
|
bar->layer_surface = zwlr_layer_shell_v1_get_layer_surface(shell, bar->surface, output, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, "doom.dwl-bar");
|
|
zwlr_layer_surface_v1_add_listener(bar->layer_surface, &layerSurfaceListener, bar);
|
|
|
|
int anchor = bar_top ? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP : ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
|
|
zwlr_layer_surface_v1_set_anchor(bar->layer_surface,
|
|
anchor | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
|
|
|
|
int height = bar_font.height + 2;
|
|
zwlr_layer_surface_v1_set_size(bar->layer_surface, 0, height);
|
|
zwlr_layer_surface_v1_set_exclusive_zone(bar->layer_surface, height);
|
|
wl_surface_commit(bar->surface);
|
|
|
|
}
|
|
|
|
int bar_is_visible(Bar* bar) {
|
|
// This is dumb, but I don't know how else to do this.
|
|
// We do a negation to convert to boolean int.
|
|
// Then another negation to get the right boolean output.
|
|
// That is 1 when there is a surface, 0 when there isn't.
|
|
return !(!bar->surface);
|
|
}
|
|
|
|
void bar_set_layout(Bar *bar, const char* text) {
|
|
pango_layout_set_text(bar->layout.layout, text, strlen(text));
|
|
}
|
|
|
|
void bar_set_title(Bar *bar, const char* text) {
|
|
pango_layout_set_text(bar->title.layout, text, strlen(text));
|
|
}
|
|
|
|
void bar_set_status(Bar *bar, const char* text) {
|
|
pango_layout_set_text(bar->status.layout, text, strlen(text));
|
|
}
|
|
|
|
void bar_set_active(Bar* bar, uint is_active) {
|
|
bar->active = is_active;
|
|
}
|
|
|
|
void bar_set_floating(Bar* bar, uint is_floating) {
|
|
bar->floating = is_floating;
|
|
}
|
|
|
|
void bar_set_tag(Bar *bar, uint i, uint state, uint occupied, uint focusedClient) {
|
|
Tag* tag = &bar->tags[i];
|
|
tag->focusedClient = focusedClient;
|
|
tag->occupied = occupied;
|
|
tag->state = state;
|
|
}
|
|
|
|
wl_surface* bar_get_surface(Bar *bar) {
|
|
return bar->surface;
|
|
}
|
|
|
|
void bar_click(Bar* bar, struct Monitor* monitor, int x, int y, uint32_t button) {
|
|
Arg *argp = NULL, arg;
|
|
Clicked location = Click_None;
|
|
|
|
if (x < bar->tags[LENGTH(bar->tags)-1].component.x) {
|
|
location = Click_Tag;
|
|
for (int i = 0; i < LENGTH(bar->tags); i++) {
|
|
if (x < bar->tags[i].component.x) {
|
|
arg.ui = 1<<i;
|
|
argp = &arg;
|
|
break;
|
|
}
|
|
}
|
|
} else if (x < bar->layout.x) {
|
|
location = Click_Layout;
|
|
} else if (x < bar->title.x) {
|
|
location = Click_Title;
|
|
} else {
|
|
location = Click_Status;
|
|
}
|
|
|
|
if (location == Click_None)
|
|
return;
|
|
|
|
for (int i = 0; i < LENGTH(buttons); i++) {
|
|
if (buttons[i].location == location && buttons[i].button == button) {
|
|
buttons[i].func(monitor, argp ? argp : &buttons[i].arg);
|
|
return;
|
|
}
|
|
}
|
|
}
|