Initial implementation of configurable GLESv2 drop shadows

This commit is contained in:
topisani 2019-06-15 11:10:24 +02:00
parent bdd4d69205
commit 678aee73e5
13 changed files with 453 additions and 5 deletions

View file

@ -190,6 +190,10 @@ sway_cmd cmd_workspace;
sway_cmd cmd_workspace_layout;
sway_cmd cmd_ws_auto_back_and_forth;
sway_cmd cmd_xwayland;
sway_cmd cmd_shadows_focused;
sway_cmd cmd_shadows_focused_inactive;
sway_cmd cmd_shadows_unfocused;
sway_cmd cmd_shadows_urgent;
sway_cmd bar_cmd_bindcode;
sway_cmd bar_cmd_binding_mode_indicator;

View file

@ -317,6 +317,12 @@ struct border_colors {
float child_border[4];
};
struct shadow_config {
int offset;
int radius;
float color[4];
};
enum edge_border_types {
E_NONE, /**< Don't hide edge borders */
E_VERTICAL, /**< hide vertical edge borders */
@ -504,6 +510,15 @@ struct sway_config {
float background[4];
} border_colors;
#if HAVE_SHADOWS
struct {
struct shadow_config focused;
struct shadow_config focused_inactive;
struct shadow_config unfocused;
struct shadow_config urgent;
} shadow_config;
#endif
// floating view
int32_t floating_maximum_width;
int32_t floating_maximum_height;

View file

@ -101,6 +101,8 @@ struct sway_workspace *output_get_active_workspace(struct sway_output *output);
void output_render(struct sway_output *output, struct timespec *when,
pixman_region32_t *damage);
void output_init_shadow_shader();
void output_surface_for_each_surface(struct sway_output *output,
struct wlr_surface *surface, double ox, double oy,
sway_surface_iterator_func_t iterator, void *user_data);

View file

@ -87,6 +87,7 @@ if get_option('tray').enabled() and not tray_deps_found
endif
have_tray = (not get_option('tray').disabled()) and tray_deps_found
have_shadows = (not get_option('shadows').disabled())
conf_data = configuration_data()
conf_data.set10('HAVE_XWAYLAND', have_xwayland)
@ -94,6 +95,7 @@ conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found())
conf_data.set10('HAVE_SYSTEMD', systemd.found())
conf_data.set10('HAVE_ELOGIND', elogind.found())
conf_data.set10('HAVE_TRAY', have_tray)
conf_data.set10('HAVE_SHADOWS', have_shadows)
scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages'))
if scdoc.found()
@ -244,6 +246,7 @@ status = [
'elogind: @0@'.format(elogind.found()),
'tray: @0@'.format(have_tray),
'man-pages: @0@'.format(scdoc.found()),
'shadows: @0@'.format(have_shadows),
'',
]
message('\n'.join(status))

View file

@ -6,3 +6,4 @@ option('xwayland', type: 'feature', value: 'auto', description: 'Enable support
option('tray', type: 'feature', value: 'auto', description: 'Enable support for swaybar tray')
option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats in swaybg')
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
option('shadows', type: 'feature', value: 'auto', description: 'Enable GLES2 drop shadow support')

View file

@ -57,8 +57,8 @@ static struct cmd_handler handlers[] = {
{ "client.urgent", cmd_client_urgent },
{ "default_border", cmd_default_border },
{ "default_floating_border", cmd_default_floating_border },
{ "exec", cmd_exec },
{ "exec_always", cmd_exec_always },
{ "exec", cmd_exec },
{ "floating_maximum_size", cmd_floating_maximum_size },
{ "floating_minimum_size", cmd_floating_minimum_size },
{ "floating_modifier", cmd_floating_modifier },
@ -67,9 +67,9 @@ static struct cmd_handler handlers[] = {
{ "focus_on_window_activation", cmd_focus_on_window_activation },
{ "focus_wrapping", cmd_focus_wrapping },
{ "font", cmd_font },
{ "for_window", cmd_for_window },
{ "force_display_urgency_hint", cmd_force_display_urgency_hint },
{ "force_focus_wrapping", cmd_force_focus_wrapping },
{ "for_window", cmd_for_window },
{ "fullscreen", cmd_fullscreen },
{ "gaps", cmd_gaps },
{ "hide_edge_borders", cmd_hide_edge_borders },
@ -84,6 +84,10 @@ static struct cmd_handler handlers[] = {
{ "popup_during_fullscreen", cmd_popup_during_fullscreen },
{ "seat", cmd_seat },
{ "set", cmd_set },
{ "shadows.focused", cmd_shadows_focused},
{ "shadows.focused_inactive", cmd_shadows_focused_inactive},
{ "shadows.unfocused", cmd_shadows_unfocused},
{ "shadows.urgent", cmd_shadows_urgent},
{ "show_marks", cmd_show_marks },
{ "smart_borders", cmd_smart_borders },
{ "smart_gaps", cmd_smart_gaps },
@ -95,8 +99,8 @@ static struct cmd_handler handlers[] = {
{ "unbindcode", cmd_unbindcode },
{ "unbindswitch", cmd_unbindswitch },
{ "unbindsym", cmd_unbindsym },
{ "workspace", cmd_workspace },
{ "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth },
{ "workspace", cmd_workspace },
};
/* Config-time only commands. Keep alphabetized */

View file

@ -117,3 +117,4 @@ struct cmd_results *cmd_client_noop(int argc, char **argv) {
sway_log(SWAY_INFO, "Warning: %s is ignored by sway", argv[-1]);
return cmd_results_new(CMD_SUCCESS, NULL);
}

91
sway/commands/shadows.c Normal file
View file

@ -0,0 +1,91 @@
#include "log.h"
#include "sway/commands.h"
#include "sway/config.h"
#include "sway/output.h"
#include "sway/tree/container.h"
/**
* Parse the hex string into an integer.
*/
static bool parse_color_int(char *hexstring, uint32_t *dest) {
if (hexstring[0] != '#') {
return false;
}
if (strlen(hexstring) != 7 && strlen(hexstring) != 9) {
return false;
}
++hexstring;
char *end;
uint32_t decimal = strtol(hexstring, &end, 16);
if (*end != '\0') {
return false;
}
if (strlen(hexstring) == 6) {
// Add alpha
decimal = (decimal << 8) | 0xff;
}
*dest = decimal;
return true;
}
/**
* Parse the hex string into a float value.
*/
static bool parse_color_float(char *hexstring, float dest[static 4]) {
uint32_t decimal;
if (!parse_color_int(hexstring, &decimal)) {
return false;
}
dest[0] = ((decimal >> 24) & 0xff) / 255.0;
dest[1] = ((decimal >> 16) & 0xff) / 255.0;
dest[2] = ((decimal >> 8) & 0xff) / 255.0;
dest[3] = (decimal & 0xff) / 255.0;
return true;
}
static struct cmd_results *handle_shadow_cmd(int argc, char **argv, struct shadow_config* class, const char* cmd_name) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 3))) {
return error;
}
class->radius = atoi(argv[0]);
class->offset = atoi(argv[1]);
if (!parse_color_float(argv[2], class->color)) {
return cmd_results_new(CMD_INVALID,
"Unable to parse shadow color '%s'", argv[0]);
}
if (config->active) {
for (int i = 0; i < root->outputs->length; ++i) {
struct sway_output *output = root->outputs->items[i];
output_damage_whole(output);
}
}
return cmd_results_new(CMD_SUCCESS, NULL);
}
struct cmd_results *cmd_shadows_focused(int argc, char **argv) {
return handle_shadow_cmd(argc, argv, &config->shadow_config.focused, "shadows.focused");
}
struct cmd_results *cmd_shadows_focused_inactive(int argc, char **argv) {
return handle_shadow_cmd(argc, argv, &config->shadow_config.focused_inactive, "shadows.focused_inactive");
}
struct cmd_results *cmd_shadows_unfocused(int argc, char **argv) {
return handle_shadow_cmd(argc, argv, &config->shadow_config.unfocused, "shadows.unfocused");
}
struct cmd_results *cmd_shadows_urgent(int argc, char **argv) {
return handle_shadow_cmd(argc, argv, &config->shadow_config.urgent, "shadows.urgent");
}

View file

@ -331,6 +331,22 @@ static void config_defaults(struct sway_config *config) {
set_color(config->border_colors.background, 0xFFFFFF);
config->shadow_config.focused.radius = 25;
config->shadow_config.focused.offset = 5;
set_color(config->shadow_config.focused.color, 0x203040);
config->shadow_config.focused_inactive.radius = 25;
config->shadow_config.focused_inactive.offset = 5;
set_color(config->shadow_config.focused_inactive.color, 0x404040);
config->shadow_config.unfocused.radius = 25;
config->shadow_config.unfocused.offset = 5;
set_color(config->shadow_config.unfocused.color, 0x000000);
config->shadow_config.urgent.radius = 25;
config->shadow_config.urgent.offset = 5;
set_color(config->shadow_config.urgent.color, 0x402020);
// Security
if (!(config->command_policies = create_list())) goto cleanup;
if (!(config->feature_policies = create_list())) goto cleanup;

View file

@ -27,6 +27,10 @@
#include "sway/tree/view.h"
#include "sway/tree/workspace.h"
#if HAVE_SHADOWS
#include <GLES2/gl2.h>
#endif
struct render_data {
pixman_region32_t *damage;
float alpha;
@ -269,12 +273,238 @@ static void render_saved_view(struct sway_view *view,
&box, matrix, alpha);
}
#if HAVE_SHADOWS
static bool check_shader_compilation(unsigned shader, const char* type) {
int success;
char infoLog[1024];
if (strcmp(type,"PROGRAM") != 0) {
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
sway_log(SWAY_ERROR, "ERROR::SHADER_COMPILATION_ERROR of type: %s ", type);
sway_log(SWAY_ERROR, "%s", infoLog);
sway_log(SWAY_ERROR, " -- --------------------------------------------------- -- ");
}
} else {
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
sway_log(SWAY_ERROR, "ERROR::SHADER_LINKING_ERROR of type: %s ", type);
sway_log(SWAY_ERROR, "%s", infoLog);
sway_log(SWAY_ERROR, " -- --------------------------------------------------- -- ");
}
}
return !!success;
}
static unsigned shadow_shader = 0;
void output_init_shadow_shader() {
if (shadow_shader != 0) return;
sway_log(SWAY_INFO, "Initializing shadow shader");
static const char* vcode =
"uniform mat3 proj;\n"
"uniform vec4 color;\n"
"attribute vec2 pos;\n"
"attribute vec2 texcoord;\n"
"varying vec4 v_color;\n"
"varying vec2 v_texcoord;\n"
"\n"
"void main() {\n"
" gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);\n"
" v_color = color;\n"
" v_texcoord = texcoord;\n"
"}\n";
static const char* fcode =
"precision mediump float; \n"
"varying vec4 v_color; \n"
"varying vec2 v_texcoord; \n"
"uniform float aspect; \n"
"uniform float radius; \n"
" \n"
"void main() \n"
"{ \n"
" vec2 pos = v_texcoord; \n"
" \n"
" //pos.x /= aspect; \n"
" \n"
" float dx = smoothstep(0.0, radius * aspect, min(pos.x, 1.0 - pos.x)); \n"
" float dy = smoothstep(0.0, radius, min(pos.y, 1.0 - pos.y)); \n"
" \n"
" float alpha = dx * dy; \n"
" gl_FragColor = vec4(v_color.rgb, v_color.a * alpha); \n"
"} \n";
unsigned int vertex, fragment;
unsigned int shader;
vertex = glCreateShader(GL_VERTEX_SHADER);
sway_log(SWAY_INFO, "vertex shader: %d", vertex);
glShaderSource(vertex, 1, &vcode, NULL);
glCompileShader(vertex);
bool failed = !check_shader_compilation(vertex, "VERTEX");
fragment = glCreateShader(GL_FRAGMENT_SHADER);
sway_log(SWAY_INFO, "fragment shader: %d", fragment);
glShaderSource(fragment, 1, &fcode, NULL);
glCompileShader(fragment);
failed |= !check_shader_compilation(fragment, "FRAGMENT");
shader = glCreateProgram();
glAttachShader(shader, vertex);
glAttachShader(shader, fragment);
glLinkProgram(shader);
failed |= !check_shader_compilation(shader, "PROGRAM");
glDeleteShader(vertex);
glDeleteShader(fragment);
if (!failed) {
shadow_shader = shader;
} else {
sway_log(SWAY_ERROR, "Failure initializing shadow shader");
}
}
static void draw_quad()
{
GLfloat verts[] = {
1, 0, // top right
0, 0, // top left
1, 1, // bottom right
0, 1, // bottom left
};
GLfloat texcoord[] = {
1, 0, // top right
0, 0, // top left
1, 1, // bottom right
0, 1, // bottom left
};
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texcoord);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
}
static void render_shadow(struct sway_output *output, pixman_region32_t *output_damage, struct wlr_box box,
float rotation, float alpha, struct shadow_config* shadow) {
if (shadow_shader == 0) return;
float offset = shadow->offset, radius = shadow->radius;
box.x += offset - radius / 2.f;
box.y += offset - radius / 2.f;
box.height += radius;
box.width += radius;
struct wlr_output *wlr_output = output->wlr_output;
// struct wlr_renderer *renderer =
// wlr_backend_get_renderer(wlr_output->backend);
box.x -= output->lx * wlr_output->scale;
box.y -= output->ly * wlr_output->scale;
pixman_region32_t damage;
pixman_region32_init(&damage);
pixman_region32_union_rect(
&damage, &damage, box.x, box.y, box.width, box.height);
pixman_region32_intersect(&damage, &damage, output_damage);
bool damaged = pixman_region32_not_empty(&damage);
if (!damaged) {
goto damage_finish;
}
float matrix[9];
wlr_matrix_project_box(matrix,
&box,
WL_OUTPUT_TRANSFORM_NORMAL,
rotation,
wlr_output->transform_matrix);
int nrects;
pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
for (int i = 0; i < nrects; ++i) {
scissor_output(output->wlr_output, &rects[i]);
float transposition[9];
wlr_matrix_transpose(transposition, matrix);
// save current shader and blend settings
GLint prevShader;
glGetIntegerv(GL_CURRENT_PROGRAM, &prevShader);
GLboolean blendEnabled;
glGetBooleanv(GL_BLEND, &blendEnabled);
GLint blendSrc;
GLint blendDst;
glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrc);
glGetIntegerv(GL_BLEND_DST_ALPHA, &blendDst);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(shadow_shader);
// update the uniform color
glUniformMatrix3fv(glGetUniformLocation(shadow_shader, "proj"),
1,
false,
transposition);
glUniform4f(glGetUniformLocation(shadow_shader, "color"),
shadow->color[0],
shadow->color[1],
shadow->color[2],
alpha);
glUniform1f(glGetUniformLocation(shadow_shader, "aspect"),
box.height / (float)box.width);
glUniform1f(glGetUniformLocation(shadow_shader, "radius"),
radius / (float)box.height);
draw_quad();
// restore state
glUseProgram(prevShader);
if (blendEnabled) glEnable(GL_BLEND);
else glDisable(GL_BLEND);
glBlendFunc(blendSrc, blendDst);
}
damage_finish:
pixman_region32_fini(&damage);
}
static void render_shadow_for_view(struct sway_output *output, pixman_region32_t *damage,
struct sway_container *con, struct shadow_config *shadow) {
float output_scale = output->wlr_output->scale;
struct sway_container_state *state = &con->current;
struct wlr_box shadow_box = {state->x, state->content_y, state->width, state->height};
scale_box(&shadow_box, output_scale);
render_shadow(output, damage, shadow_box, 0, con->alpha, shadow);
}
#else
void output_init_shadow_shader() {}
static void render_shadow_for_view(struct sway_output *output, pixman_region32_t *damage,
struct sway_container *con, struct shadow_config *shadow) {
#endif
// SHADOWS END
/**
* Render a view's surface and left/bottom/right borders.
*/
static void render_view(struct sway_output *output, pixman_region32_t *damage,
struct sway_container *con, struct border_colors *colors) {
struct sway_view *view = con->view;
if (view->saved_buffer) {
render_saved_view(view, output, damage, view->container->alpha);
} else if (view->surface) {
@ -285,11 +515,12 @@ static void render_view(struct sway_output *output, pixman_region32_t *damage,
return;
}
struct wlr_box box;
float output_scale = output->wlr_output->scale;
float color[4];
struct sway_container_state *state = &con->current;
struct wlr_box box;
float color[4];
if (state->border_left) {
memcpy(&color, colors->child_border, sizeof(float) * 4);
premultiply_alpha(color, con->alpha);
@ -678,28 +909,35 @@ static void render_containers_linear(struct sway_output *output,
if (child->view) {
struct sway_view *view = child->view;
struct border_colors *colors;
struct shadow_config *shadow;
struct wlr_texture *title_texture;
struct wlr_texture *marks_texture;
struct sway_container_state *state = &child->current;
if (view_is_urgent(view)) {
colors = &config->border_colors.urgent;
shadow = &config->shadow_config.urgent;
title_texture = child->title_urgent;
marks_texture = child->marks_urgent;
} else if (state->focused || parent->focused) {
colors = &config->border_colors.focused;
shadow = &config->shadow_config.focused;
title_texture = child->title_focused;
marks_texture = child->marks_focused;
} else if (child == parent->active_child) {
colors = &config->border_colors.focused_inactive;
shadow = &config->shadow_config.focused_inactive;
title_texture = child->title_focused_inactive;
marks_texture = child->marks_focused_inactive;
} else {
colors = &config->border_colors.unfocused;
shadow = &config->shadow_config.unfocused;
title_texture = child->title_unfocused;
marks_texture = child->marks_unfocused;
}
render_shadow_for_view(output, damage, child, shadow);
if (state->border == B_NORMAL) {
render_titlebar(output, damage, child, state->x,
state->y, state->width, colors,
@ -727,6 +965,32 @@ static void render_containers_tabbed(struct sway_output *output,
struct border_colors *current_colors = &config->border_colors.unfocused;
int tab_width = parent->box.width / parent->children->length;
#if HAVE_SHADOWS
struct shadow_config *current_shadow = &config->shadow_config.unfocused;
// Render shadow
for (int i = 0; i < parent->children->length; ++i) {
struct sway_container *child = parent->children->items[i];
struct sway_view *view = child->view;
struct sway_container_state *cstate = &child->current;
bool urgent = view ?
view_is_urgent(view) : container_has_urgent_child(child);
if (child == current) {
if (urgent) {
current_shadow = &config->shadow_config.urgent;
} else if (cstate->focused || parent->focused) {
current_shadow = &config->shadow_config.focused;
} else if (child == parent->active_child) {
current_shadow = &config->shadow_config.focused_inactive;
} else {
current_shadow = &config->shadow_config.unfocused;
}
}
}
render_shadow_for_view(output, damage, current, current_shadow);
#endif
// Render tabs
for (int i = 0; i < parent->children->length; ++i) {
struct sway_container *child = parent->children->items[i];
@ -790,14 +1054,42 @@ static void render_containers_stacked(struct sway_output *output,
}
struct sway_container *current = parent->active_child;
struct border_colors *current_colors = &config->border_colors.unfocused;
struct shadow_config *current_shadows = &config->shadow_config.unfocused;
size_t titlebar_height = container_titlebar_height();
#if HAVE_SHADOWS
struct shadow_config *current_shadow = &config->shadow_config.unfocused;
// Render shadow
for (int i = 0; i < parent->children->length; ++i) {
struct sway_container *child = parent->children->items[i];
struct sway_view *view = child->view;
struct sway_container_state *cstate = &child->current;
bool urgent = view ?
view_is_urgent(view) : container_has_urgent_child(child);
if (child == current) {
if (urgent) {
current_shadow = &config->shadow_config.urgent;
} else if (cstate->focused || parent->focused) {
current_shadow = &config->shadow_config.focused;
} else if (child == parent->active_child) {
current_shadow = &config->shadow_config.focused_inactive;
} else {
current_shadow = &config->shadow_config.unfocused;
}
}
}
render_shadow_for_view(output, damage, current, current_shadow);
#endif
// Render titles
for (int i = 0; i < parent->children->length; ++i) {
struct sway_container *child = parent->children->items[i];
struct sway_view *view = child->view;
struct sway_container_state *cstate = &child->current;
struct border_colors *colors;
struct shadow_config *shadows;
struct wlr_texture *title_texture;
struct wlr_texture *marks_texture;
bool urgent = view ?
@ -805,18 +1097,22 @@ static void render_containers_stacked(struct sway_output *output,
if (urgent) {
colors = &config->border_colors.urgent;
shadows = &config->shadow_config.urgent;
title_texture = child->title_urgent;
marks_texture = child->marks_urgent;
} else if (cstate->focused || parent->focused) {
colors = &config->border_colors.focused;
shadows = &config->shadow_config.focused;
title_texture = child->title_focused;
marks_texture = child->marks_focused;
} else if (child == parent->active_child) {
colors = &config->border_colors.focused_inactive;
shadows = &config->shadow_config.focused_inactive;
title_texture = child->title_focused_inactive;
marks_texture = child->marks_focused_inactive;
} else {
colors = &config->border_colors.unfocused;
shadows = &config->shadow_config.unfocused;
title_texture = child->title_unfocused;
marks_texture = child->marks_unfocused;
}
@ -827,6 +1123,7 @@ static void render_containers_stacked(struct sway_output *output,
if (child == current) {
current_colors = colors;
current_shadows = shadows;
}
}
@ -903,23 +1200,29 @@ static void render_floating_container(struct sway_output *soutput,
if (con->view) {
struct sway_view *view = con->view;
struct border_colors *colors;
struct shadow_config *shadows;
struct wlr_texture *title_texture;
struct wlr_texture *marks_texture;
if (view_is_urgent(view)) {
colors = &config->border_colors.urgent;
shadows = &config->shadow_config.urgent;
title_texture = con->title_urgent;
marks_texture = con->marks_urgent;
} else if (con->current.focused) {
colors = &config->border_colors.focused;
shadows = &config->shadow_config.focused;
title_texture = con->title_focused;
marks_texture = con->marks_focused;
} else {
colors = &config->border_colors.unfocused;
shadows = &config->shadow_config.unfocused;
title_texture = con->title_unfocused;
marks_texture = con->marks_unfocused;
}
render_shadow_for_view(soutput, damage, con, shadows);
if (con->current.border == B_NORMAL) {
render_titlebar(soutput, damage, con, con->current.x,
con->current.y, con->current.width, colors,

View file

@ -339,6 +339,11 @@ static void ipc_json_describe_workspace(struct sway_workspace *workspace,
json_object_new_string(
ipc_json_orientation_description(workspace->layout)));
json_object *gaps = json_object_new_object();
json_object_object_add(gaps, "inner", json_object_new_int(workspace->gaps_inner));
// Add outer
json_object_object_add(object, "gaps", gaps);
// Floating
json_object *floating_array = json_object_new_array();
for (int i = 0; i < workspace->floating->length; ++i) {

View file

@ -111,6 +111,7 @@ sway_sources = files(
'commands/workspace_layout.c',
'commands/ws_auto_back_and_forth.c',
'commands/xwayland.c',
'commands/shadows.c',
'commands/bar/bind.c',
'commands/bar/binding_mode_indicator.c',

View file

@ -101,6 +101,8 @@ struct sway_output *output_create(struct wlr_output *wlr_output) {
output->workspaces = create_list();
output->current.workspaces = create_list();
output_init_shadow_shader();
return output;
}