cca2436ba5
The few places that required a surface commit relied on render_frame and render_frame_background to form it form them after they had set up frame callback events. This would fail if render_frame ran out of buffers and exited early, as the caller would still wait indefinitely on the frame callback. swaylock would quite consistently run out of buffers when rendering immediately after a configure event, such as when the keypress causing render also caused outputs to be enabled from idle. Restructure the render and commit handling slightly so that the whole frame callback and commit setup is handled by the render code, which now has a single render entrypoint. This both avoids stalls from lacking commits, but also fixes the case the configure path to respect frame callbacks so we do not run out of buffers in the first place.
388 lines
13 KiB
C
388 lines
13 KiB
C
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <wayland-client.h>
|
|
#include "cairo.h"
|
|
#include "background-image.h"
|
|
#include "swaylock.h"
|
|
#include "log.h"
|
|
|
|
#define M_PI 3.14159265358979323846
|
|
const float TYPE_INDICATOR_RANGE = M_PI / 3.0f;
|
|
const float TYPE_INDICATOR_BORDER_THICKNESS = M_PI / 128.0f;
|
|
|
|
static void set_color_for_state(cairo_t *cairo, struct swaylock_state *state,
|
|
struct swaylock_colorset *colorset) {
|
|
if (state->input_state == INPUT_STATE_CLEAR) {
|
|
cairo_set_source_u32(cairo, colorset->cleared);
|
|
} else if (state->auth_state == AUTH_STATE_VALIDATING) {
|
|
cairo_set_source_u32(cairo, colorset->verifying);
|
|
} else if (state->auth_state == AUTH_STATE_INVALID) {
|
|
cairo_set_source_u32(cairo, colorset->wrong);
|
|
} else {
|
|
if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) {
|
|
cairo_set_source_u32(cairo, colorset->caps_lock);
|
|
} else if (state->xkb.caps_lock && !state->args.show_caps_lock_indicator &&
|
|
state->args.show_caps_lock_text) {
|
|
uint32_t inputtextcolor = state->args.colors.text.input;
|
|
state->args.colors.text.input = state->args.colors.text.caps_lock;
|
|
cairo_set_source_u32(cairo, colorset->input);
|
|
state->args.colors.text.input = inputtextcolor;
|
|
} else {
|
|
cairo_set_source_u32(cairo, colorset->input);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void surface_frame_handle_done(void *data, struct wl_callback *callback,
|
|
uint32_t time) {
|
|
struct swaylock_surface *surface = data;
|
|
|
|
wl_callback_destroy(callback);
|
|
surface->frame = NULL;
|
|
|
|
render(surface);
|
|
}
|
|
|
|
static const struct wl_callback_listener surface_frame_listener = {
|
|
.done = surface_frame_handle_done,
|
|
};
|
|
|
|
static bool render_frame(struct swaylock_surface *surface);
|
|
|
|
void render(struct swaylock_surface *surface) {
|
|
struct swaylock_state *state = surface->state;
|
|
|
|
int buffer_width = surface->width * surface->scale;
|
|
int buffer_height = surface->height * surface->scale;
|
|
if (buffer_width == 0 || buffer_height == 0) {
|
|
return; // not yet configured
|
|
}
|
|
|
|
if (!surface->dirty || surface->frame) {
|
|
// Nothing to do or frame already pending
|
|
return;
|
|
}
|
|
|
|
bool need_destroy = false;
|
|
struct pool_buffer buffer;
|
|
|
|
if (buffer_width != surface->last_buffer_width ||
|
|
buffer_height != surface->last_buffer_height) {
|
|
need_destroy = true;
|
|
if (!create_buffer(state->shm, &buffer, buffer_width, buffer_height,
|
|
WL_SHM_FORMAT_ARGB8888)) {
|
|
swaylock_log(LOG_ERROR,
|
|
"Failed to create new buffer for frame background.");
|
|
return;
|
|
}
|
|
|
|
cairo_t *cairo = buffer.cairo;
|
|
cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
|
|
|
|
cairo_save(cairo);
|
|
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
|
|
cairo_set_source_u32(cairo, state->args.colors.background);
|
|
cairo_paint(cairo);
|
|
if (surface->image && state->args.mode != BACKGROUND_MODE_SOLID_COLOR) {
|
|
cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
|
|
render_background_image(cairo, surface->image,
|
|
state->args.mode, buffer_width, buffer_height);
|
|
}
|
|
cairo_restore(cairo);
|
|
cairo_identity_matrix(cairo);
|
|
|
|
wl_surface_set_buffer_scale(surface->surface, surface->scale);
|
|
wl_surface_attach(surface->surface, buffer.buffer, 0, 0);
|
|
wl_surface_damage_buffer(surface->surface, 0, 0, INT32_MAX, INT32_MAX);
|
|
need_destroy = true;
|
|
|
|
surface->last_buffer_width = buffer_width;
|
|
surface->last_buffer_height = buffer_height;
|
|
}
|
|
|
|
render_frame(surface);
|
|
surface->dirty = false;
|
|
surface->frame = wl_surface_frame(surface->surface);
|
|
wl_callback_add_listener(surface->frame, &surface_frame_listener, surface);
|
|
wl_surface_commit(surface->surface);
|
|
|
|
if (need_destroy) {
|
|
destroy_buffer(&buffer);
|
|
}
|
|
}
|
|
|
|
static void configure_font_drawing(cairo_t *cairo, struct swaylock_state *state,
|
|
enum wl_output_subpixel subpixel, int arc_radius) {
|
|
cairo_font_options_t *fo = cairo_font_options_create();
|
|
cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
|
|
cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
|
|
cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(subpixel));
|
|
|
|
cairo_set_font_options(cairo, fo);
|
|
cairo_select_font_face(cairo, state->args.font,
|
|
CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
|
if (state->args.font_size > 0) {
|
|
cairo_set_font_size(cairo, state->args.font_size);
|
|
} else {
|
|
cairo_set_font_size(cairo, arc_radius / 3.0f);
|
|
}
|
|
cairo_font_options_destroy(fo);
|
|
}
|
|
|
|
static bool render_frame(struct swaylock_surface *surface) {
|
|
struct swaylock_state *state = surface->state;
|
|
|
|
// First, compute the text that will be drawn, if any, since this
|
|
// determines the size/positioning of the surface
|
|
|
|
char attempts[4]; // like i3lock: count no more than 999
|
|
char *text = NULL;
|
|
const char *layout_text = NULL;
|
|
|
|
bool draw_indicator = state->args.show_indicator &&
|
|
(state->auth_state != AUTH_STATE_IDLE ||
|
|
state->input_state != INPUT_STATE_IDLE ||
|
|
state->args.indicator_idle_visible);
|
|
|
|
if (draw_indicator) {
|
|
if (state->input_state == INPUT_STATE_CLEAR) {
|
|
// This message has highest priority
|
|
text = "Cleared";
|
|
} else if (state->auth_state == AUTH_STATE_VALIDATING) {
|
|
text = "Verifying";
|
|
} else if (state->auth_state == AUTH_STATE_INVALID) {
|
|
text = "Wrong";
|
|
} else {
|
|
// Caps Lock has higher priority
|
|
if (state->xkb.caps_lock && state->args.show_caps_lock_text) {
|
|
text = "Caps Lock";
|
|
} else if (state->args.show_failed_attempts &&
|
|
state->failed_attempts > 0) {
|
|
if (state->failed_attempts > 999) {
|
|
text = "999+";
|
|
} else {
|
|
snprintf(attempts, sizeof(attempts), "%d", state->failed_attempts);
|
|
text = attempts;
|
|
}
|
|
}
|
|
|
|
xkb_layout_index_t num_layout = xkb_keymap_num_layouts(state->xkb.keymap);
|
|
if (!state->args.hide_keyboard_layout &&
|
|
(state->args.show_keyboard_layout || num_layout > 1)) {
|
|
xkb_layout_index_t curr_layout = 0;
|
|
|
|
// advance to the first active layout (if any)
|
|
while (curr_layout < num_layout &&
|
|
xkb_state_layout_index_is_active(state->xkb.state,
|
|
curr_layout, XKB_STATE_LAYOUT_EFFECTIVE) != 1) {
|
|
++curr_layout;
|
|
}
|
|
// will handle invalid index if none are active
|
|
layout_text = xkb_keymap_layout_get_name(state->xkb.keymap, curr_layout);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute the size of the buffer needed
|
|
int arc_radius = state->args.radius * surface->scale;
|
|
int arc_thickness = state->args.thickness * surface->scale;
|
|
int buffer_diameter = (arc_radius + arc_thickness) * 2;
|
|
int buffer_width = buffer_diameter;
|
|
int buffer_height = buffer_diameter;
|
|
|
|
if (text || layout_text) {
|
|
cairo_set_antialias(state->test_cairo, CAIRO_ANTIALIAS_BEST);
|
|
configure_font_drawing(state->test_cairo, state, surface->subpixel, arc_radius);
|
|
|
|
if (text) {
|
|
cairo_text_extents_t extents;
|
|
cairo_text_extents(state->test_cairo, text, &extents);
|
|
if (buffer_width < extents.width) {
|
|
buffer_width = extents.width;
|
|
}
|
|
}
|
|
if (layout_text) {
|
|
cairo_text_extents_t extents;
|
|
cairo_font_extents_t fe;
|
|
double box_padding = 4.0 * surface->scale;
|
|
cairo_text_extents(state->test_cairo, layout_text, &extents);
|
|
cairo_font_extents(state->test_cairo, &fe);
|
|
buffer_height += fe.height + 2 * box_padding;
|
|
if (buffer_width < extents.width + 2 * box_padding) {
|
|
buffer_width = extents.width + 2 * box_padding;
|
|
}
|
|
}
|
|
}
|
|
// Ensure buffer size is multiple of buffer scale - required by protocol
|
|
buffer_height += surface->scale - (buffer_height % surface->scale);
|
|
buffer_width += surface->scale - (buffer_width % surface->scale);
|
|
|
|
int subsurf_xpos;
|
|
int subsurf_ypos;
|
|
|
|
// Center the indicator unless overridden by the user
|
|
if (state->args.override_indicator_x_position) {
|
|
subsurf_xpos = state->args.indicator_x_position -
|
|
buffer_width / (2 * surface->scale) + 2 / surface->scale;
|
|
} else {
|
|
subsurf_xpos = surface->width / 2 -
|
|
buffer_width / (2 * surface->scale) + 2 / surface->scale;
|
|
}
|
|
|
|
if (state->args.override_indicator_y_position) {
|
|
subsurf_ypos = state->args.indicator_y_position -
|
|
(state->args.radius + state->args.thickness);
|
|
} else {
|
|
subsurf_ypos = surface->height / 2 -
|
|
(state->args.radius + state->args.thickness);
|
|
}
|
|
|
|
struct pool_buffer *buffer = get_next_buffer(state->shm,
|
|
surface->indicator_buffers, buffer_width, buffer_height);
|
|
if (buffer == NULL) {
|
|
swaylock_log(LOG_ERROR, "No buffer");
|
|
return false;
|
|
}
|
|
|
|
// Render the buffer
|
|
cairo_t *cairo = buffer->cairo;
|
|
cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
|
|
|
|
cairo_identity_matrix(cairo);
|
|
|
|
// Clear
|
|
cairo_save(cairo);
|
|
cairo_set_source_rgba(cairo, 0, 0, 0, 0);
|
|
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
|
|
cairo_paint(cairo);
|
|
cairo_restore(cairo);
|
|
|
|
float type_indicator_border_thickness =
|
|
TYPE_INDICATOR_BORDER_THICKNESS * surface->scale;
|
|
|
|
if (draw_indicator) {
|
|
// Fill inner circle
|
|
cairo_set_line_width(cairo, 0);
|
|
cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
|
|
arc_radius - arc_thickness / 2, 0, 2 * M_PI);
|
|
set_color_for_state(cairo, state, &state->args.colors.inside);
|
|
cairo_fill_preserve(cairo);
|
|
cairo_stroke(cairo);
|
|
|
|
// Draw ring
|
|
cairo_set_line_width(cairo, arc_thickness);
|
|
cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2, arc_radius,
|
|
0, 2 * M_PI);
|
|
set_color_for_state(cairo, state, &state->args.colors.ring);
|
|
cairo_stroke(cairo);
|
|
|
|
// Draw a message
|
|
configure_font_drawing(cairo, state, surface->subpixel, arc_radius);
|
|
set_color_for_state(cairo, state, &state->args.colors.text);
|
|
|
|
if (text) {
|
|
cairo_text_extents_t extents;
|
|
cairo_font_extents_t fe;
|
|
double x, y;
|
|
cairo_text_extents(cairo, text, &extents);
|
|
cairo_font_extents(cairo, &fe);
|
|
x = (buffer_width / 2) -
|
|
(extents.width / 2 + extents.x_bearing);
|
|
y = (buffer_diameter / 2) +
|
|
(fe.height / 2 - fe.descent);
|
|
|
|
cairo_move_to(cairo, x, y);
|
|
cairo_show_text(cairo, text);
|
|
cairo_close_path(cairo);
|
|
cairo_new_sub_path(cairo);
|
|
}
|
|
|
|
// Typing indicator: Highlight random part on keypress
|
|
if (state->input_state == INPUT_STATE_LETTER ||
|
|
state->input_state == INPUT_STATE_BACKSPACE) {
|
|
double highlight_start = state->highlight_start * (M_PI / 1024.0);
|
|
cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
|
|
arc_radius, highlight_start,
|
|
highlight_start + TYPE_INDICATOR_RANGE);
|
|
if (state->input_state == INPUT_STATE_LETTER) {
|
|
if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) {
|
|
cairo_set_source_u32(cairo, state->args.colors.caps_lock_key_highlight);
|
|
} else {
|
|
cairo_set_source_u32(cairo, state->args.colors.key_highlight);
|
|
}
|
|
} else {
|
|
if (state->xkb.caps_lock && state->args.show_caps_lock_indicator) {
|
|
cairo_set_source_u32(cairo, state->args.colors.caps_lock_bs_highlight);
|
|
} else {
|
|
cairo_set_source_u32(cairo, state->args.colors.bs_highlight);
|
|
}
|
|
}
|
|
cairo_stroke(cairo);
|
|
|
|
// Draw borders
|
|
cairo_set_source_u32(cairo, state->args.colors.separator);
|
|
cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
|
|
arc_radius, highlight_start,
|
|
highlight_start + type_indicator_border_thickness);
|
|
cairo_stroke(cairo);
|
|
|
|
cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
|
|
arc_radius, highlight_start + TYPE_INDICATOR_RANGE,
|
|
highlight_start + TYPE_INDICATOR_RANGE +
|
|
type_indicator_border_thickness);
|
|
cairo_stroke(cairo);
|
|
}
|
|
|
|
// Draw inner + outer border of the circle
|
|
set_color_for_state(cairo, state, &state->args.colors.line);
|
|
cairo_set_line_width(cairo, 2.0 * surface->scale);
|
|
cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
|
|
arc_radius - arc_thickness / 2, 0, 2 * M_PI);
|
|
cairo_stroke(cairo);
|
|
cairo_arc(cairo, buffer_width / 2, buffer_diameter / 2,
|
|
arc_radius + arc_thickness / 2, 0, 2 * M_PI);
|
|
cairo_stroke(cairo);
|
|
|
|
// display layout text separately
|
|
if (layout_text) {
|
|
cairo_text_extents_t extents;
|
|
cairo_font_extents_t fe;
|
|
double x, y;
|
|
double box_padding = 4.0 * surface->scale;
|
|
cairo_text_extents(cairo, layout_text, &extents);
|
|
cairo_font_extents(cairo, &fe);
|
|
// upper left coordinates for box
|
|
x = (buffer_width / 2) - (extents.width / 2) - box_padding;
|
|
y = buffer_diameter;
|
|
|
|
// background box
|
|
cairo_rectangle(cairo, x, y,
|
|
extents.width + 2.0 * box_padding,
|
|
fe.height + 2.0 * box_padding);
|
|
cairo_set_source_u32(cairo, state->args.colors.layout_background);
|
|
cairo_fill_preserve(cairo);
|
|
// border
|
|
cairo_set_source_u32(cairo, state->args.colors.layout_border);
|
|
cairo_stroke(cairo);
|
|
|
|
// take font extents and padding into account
|
|
cairo_move_to(cairo,
|
|
x - extents.x_bearing + box_padding,
|
|
y + (fe.height - fe.descent) + box_padding);
|
|
cairo_set_source_u32(cairo, state->args.colors.layout_text);
|
|
cairo_show_text(cairo, layout_text);
|
|
cairo_new_sub_path(cairo);
|
|
}
|
|
}
|
|
|
|
// Send Wayland requests
|
|
wl_subsurface_set_position(surface->subsurface, subsurf_xpos, subsurf_ypos);
|
|
|
|
wl_surface_set_buffer_scale(surface->child, surface->scale);
|
|
wl_surface_attach(surface->child, buffer->buffer, 0, 0);
|
|
wl_surface_damage_buffer(surface->child, 0, 0, INT32_MAX, INT32_MAX);
|
|
wl_surface_commit(surface->child);
|
|
|
|
return true;
|
|
}
|