#define _POSIX_C_SOURCE 200112L #include "wl.h" #include #include #include #include #include #include #include #include #include #include #include #include "protocols/xdg-output-unstable-v1-client-header.h" #include "protocols/xdg-output-unstable-v1.h" #include "protocols/xdg-shell-client-header.h" #include "protocols/xdg-shell.h" #include "protocols/wlr-layer-shell-unstable-v1-client-header.h" #include "protocols/wlr-layer-shell-unstable-v1.h" #include "pool-buffer.h" #include "../log.h" #include "../settings.h" struct window_wl { struct wl_surface *surface; struct zwlr_layer_surface_v1 *layer_surface; struct wl_buffer *buffer; cairo_surface_t *c_surface; cairo_t * c_ctx; struct dimensions dim; char *data; size_t size; }; struct wl_ctx { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct wl_shm *shm; struct zwlr_layer_shell_v1 *layer_shell; struct zxdg_output_manager_v1 *xdg_output_manager; struct wl_list outputs; struct wl_surface *surface; struct wl_output *surface_output; struct zwlr_layer_surface_v1 *layer_surface; struct wl_output *layer_surface_output; struct wl_callback *frame_callback; bool configured; bool dirty; struct dimensions cur_dim; int32_t width, height; struct pool_buffer buffers[2]; struct pool_buffer *current_buffer; }; struct wl_output { uint32_t global_name; char *name; struct wl_output *wl_output; struct zxdg_output_v1 *xdg_output; struct wl_list link; uint32_t scale; }; struct wl_ctx ctx; static void noop() { // This space intentionally left blank } void set_dirty(); static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { struct wl_output *output = data; output->name = g_strdup(name); } static const struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = noop, .logical_size = noop, .done = noop, .name = xdg_output_handle_name, .description = noop, }; static void get_xdg_output(struct wl_output *output) { if (ctx.xdg_output_manager == NULL || output->xdg_output != NULL) { return; } output->xdg_output = zxdg_output_manager_v1_get_xdg_output( ctx.xdg_output_manager, output->wl_output); zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); } static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t phy_width, int32_t phy_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { //struct wl_output *output = data; //output->subpixel = subpixel; } static void output_handle_scale(void *data, struct wl_output *wl_output, int32_t factor) { struct wl_output *output = data; output->scale = factor; } static const struct wl_output_listener output_listener = { .geometry = output_handle_geometry, .mode = noop, .done = noop, .scale = output_handle_scale, }; static void create_output( struct wl_output *wl_output, uint32_t global_name) { struct wl_output *output = g_malloc0(sizeof(struct wl_output)); if (output == NULL) { fprintf(stderr, "allocation failed\n"); return; } output->global_name = global_name; output->wl_output = wl_output; // TODO: Fix this //output->scale = 1; //wl_list_insert(&state->outputs, &output->link); wl_output_set_user_data(wl_output, output); wl_output_add_listener(wl_output, &output_listener, output); get_xdg_output(output); } static void destroy_output(struct wl_output *output) { if (ctx.surface_output == output) { ctx.surface_output = NULL; } if (ctx.layer_surface_output == output) { ctx.layer_surface_output = NULL; } wl_list_remove(&output->link); if (output->xdg_output != NULL) { zxdg_output_v1_destroy(output->xdg_output); } wl_output_destroy(output->wl_output); free(output->name); free(output); } // FIXME: Snipped touch handling static void surface_handle_enter(void *data, struct wl_surface *surface, struct wl_output *wl_output) { // Don't bother keeping a list of outputs, a layer surface can only be on // one output a a time ctx.surface_output = wl_output_get_user_data(wl_output); set_dirty(); } static void surface_handle_leave(void *data, struct wl_surface *surface, struct wl_output *wl_output) { ctx.surface_output = NULL; } static const struct wl_surface_listener surface_listener = { .enter = surface_handle_enter, .leave = surface_handle_leave, }; static void schedule_frame_and_commit(); static void send_frame(); static void layer_surface_handle_configure(void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) { ctx.configured = true; ctx.width = width; ctx.height = height; zwlr_layer_surface_v1_ack_configure(surface, serial); send_frame(); } static void layer_surface_handle_closed(void *data, struct zwlr_layer_surface_v1 *surface) { LOG_W("Destroying layer"); zwlr_layer_surface_v1_destroy(ctx.layer_surface); ctx.layer_surface = NULL; wl_surface_destroy(ctx.surface); ctx.surface = NULL; if (ctx.frame_callback) { wl_callback_destroy(ctx.frame_callback); ctx.frame_callback = NULL; ctx.dirty = true; } if (ctx.configured) { ctx.configured = false; ctx.width = ctx.height = 0; ctx.dirty = true; } if (ctx.dirty) { schedule_frame_and_commit(); } } static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { .configure = layer_surface_handle_configure, .closed = layer_surface_handle_closed, }; static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (strcmp(interface, wl_compositor_interface.name) == 0) { ctx.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); } else if (strcmp(interface, wl_shm_interface.name) == 0) { ctx.shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { ctx.layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); } else if (strcmp(interface, wl_seat_interface.name) == 0) { struct wl_seat *seat = wl_registry_bind(registry, name, &wl_seat_interface, 3); //create_seat(seat); } else if (strcmp(interface, wl_output_interface.name) == 0) { struct wl_output *output = wl_registry_bind(registry, name, &wl_output_interface, 3); create_output(output, name); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { ctx.xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct wl_output *output, *tmp; wl_list_for_each_safe(output, tmp, &ctx.outputs, link) { if (output->global_name == name) { destroy_output(output); break; } } } static const struct wl_registry_listener registry_listener = { .global = handle_global, .global_remove = handle_global_remove, }; bool init_wayland() { wl_list_init(&ctx.outputs); //wl_list_init(&ctx.seats); ctx.display = wl_display_connect(NULL); if (ctx.display == NULL) { fprintf(stderr, "failed to create display\n"); return false; } ctx.registry = wl_display_get_registry(ctx.display); wl_registry_add_listener(ctx.registry, ®istry_listener, NULL); wl_display_roundtrip(ctx.display); if (ctx.compositor == NULL) { fprintf(stderr, "compositor doesn't support wl_compositor\n"); return false; } if (ctx.shm == NULL) { fprintf(stderr, "compositor doesn't support wl_shm\n"); return false; } if (ctx.layer_shell == NULL) { fprintf(stderr, "compositor doesn't support zwlr_layer_shell_v1\n"); return false; } if (ctx.xdg_output_manager != NULL) { struct wl_output *output; wl_list_for_each(output, &ctx.outputs, link) { get_xdg_output(output); } wl_display_roundtrip(ctx.display); } //if (ctx.xdg_output_manager == NULL && // strcmp(ctx.config.output, "") != 0) { // fprintf(stderr, "warning: configured an output but compositor doesn't " // "support xdg-output-unstable-v1 version 2\n"); //} return true; } void finish_wayland() { if (ctx.layer_surface != NULL) { zwlr_layer_surface_v1_destroy(ctx.layer_surface); } if (ctx.surface != NULL) { wl_surface_destroy(ctx.surface); } finish_buffer(&ctx.buffers[0]); finish_buffer(&ctx.buffers[1]); struct wl_output *output, *output_tmp; wl_list_for_each_safe(output, output_tmp, &ctx.outputs, link) { destroy_output(output); } //struct mako_seat *seat, *seat_tmp; //wl_list_for_each_safe(seat, seat_tmp, &ctx.seats, link) { // destroy_seat(seat); //} if (ctx.xdg_output_manager != NULL) { zxdg_output_manager_v1_destroy(ctx.xdg_output_manager); } zwlr_layer_shell_v1_destroy(ctx.layer_shell); wl_compositor_destroy(ctx.compositor); wl_shm_destroy(ctx.shm); wl_registry_destroy(ctx.registry); wl_display_disconnect(ctx.display); } // FIXME: Snip static struct wl_output *get_configured_output() { struct wl_output *output; // FIXME wl_list_for_each(output, &ctx.outputs, link) { return output; } return NULL; } static void schedule_frame_and_commit(); // Draw and commit a new frame. static void send_frame() { int scale = 1; struct wl_output *output = get_configured_output(); int height = ctx.cur_dim.h; // There are two cases where we want to tear down the surface: zero // notifications (height = 0) or moving between outputs. if (height == 0 || ctx.layer_surface_output != output) { if (ctx.layer_surface != NULL) { zwlr_layer_surface_v1_destroy(ctx.layer_surface); ctx.layer_surface = NULL; } if (ctx.surface != NULL) { wl_surface_destroy(ctx.surface); ctx.surface = NULL; } ctx.width = ctx.height = 0; ctx.surface_output = NULL; ctx.configured = false; } // If there are no notifications, there's no point in recreating the // surface right now. if (height == 0) { ctx.dirty = false; wl_display_roundtrip(ctx.display); return; } // If we've made it here, there is something to draw. If the surface // doesn't exist (this is the first notification, or we moved to a // different output), we need to create it. if (ctx.layer_surface == NULL) { struct wl_output *wl_output = NULL; if (output != NULL) { wl_output = output->wl_output; } ctx.layer_surface_output = output; ctx.surface = wl_compositor_create_surface(ctx.compositor); wl_surface_add_listener(ctx.surface, &surface_listener, NULL); ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface( ctx.layer_shell, ctx.surface, wl_output, ZWLR_LAYER_SHELL_V1_LAYER_TOP, "notifications"); zwlr_layer_surface_v1_add_listener(ctx.layer_surface, &layer_surface_listener, NULL); // Because we're creating a new surface, we aren't going to draw // anything into it during this call. We don't know what size the // surface will be until we've asked the compositor for what we want // and it has responded with what it actually gave us. We also know // that the height we would _like_ to draw (greater than zero, or we // would have bailed already) is different from our ctx.height // (which has to be zero here), so we can fall through to the next // block to let it set the size for us. } assert(ctx.layer_surface); // We now want to resize the surface if it isn't the right size. If the // surface is brand new, it doesn't even have a size yet. If it already // exists, we might need to resize if the list of notifications has changed // since the last time we drew. if (ctx.height != height) { struct dimensions dim = ctx.cur_dim; // Set window size zwlr_layer_surface_v1_set_size(ctx.layer_surface, dim.w, dim.h); uint32_t anchor = 0; if (settings.geometry.negative_x){ anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; } else{ anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; } if (settings.geometry.negative_y){ anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; } else{ anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; } // Put the window at the right position zwlr_layer_surface_v1_set_anchor(ctx.layer_surface, anchor); zwlr_layer_surface_v1_set_margin(ctx.layer_surface, abs(settings.geometry.y), // top abs(settings.geometry.x), // right abs(settings.geometry.y), // bottom abs(settings.geometry.x));// left wl_surface_commit(ctx.surface); // Now we're going to bail without drawing anything. This gives the // compositor a chance to create the surface and tell us what size we // were actually granted, which may be smaller than what we asked for // depending on the screen size and layout of other layer surfaces. // This information is provided in layer_surface_handle_configure, // which will then call send_frame again. When that call happens, the // layer surface will exist and the height will hopefully match what // we asked for. That means we won't return here, and will actually // draw into the surface down below. // TODO: If the compositor doesn't send a configure with the size we // requested, we'll enter an infinite loop. We need to keep track of // the fact that a request was sent separately from what height we are. wl_display_roundtrip(ctx.display); return; } assert(ctx.configured); // Yay we can finally draw something! //struct wl_region *input_region = get_input_region(); //wl_surface_set_input_region(ctx.surface, input_region); //wl_region_destroy(input_region); wl_surface_set_buffer_scale(ctx.surface, scale); wl_surface_damage_buffer(ctx.surface, 0, 0, INT32_MAX, INT32_MAX); wl_surface_attach(ctx.surface, ctx.current_buffer->buffer, 0, 0); ctx.current_buffer->busy = true; // Schedule a frame in case the state becomes dirty again schedule_frame_and_commit(); ctx.dirty = false; } static void frame_handle_done(void *data, struct wl_callback *callback, uint32_t time) { wl_callback_destroy(ctx.frame_callback); ctx.frame_callback = NULL; // Only draw again if we need to if (ctx.dirty) { send_frame(); } } static const struct wl_callback_listener frame_listener = { .done = frame_handle_done, }; static void schedule_frame_and_commit() { if (ctx.frame_callback) { return; } if (ctx.surface == NULL) { // We don't yet have a surface, create it immediately send_frame(); return; } ctx.frame_callback = wl_surface_frame(ctx.surface); wl_callback_add_listener(ctx.frame_callback, &frame_listener, NULL); wl_surface_commit(ctx.surface); } void set_dirty() { if (ctx.dirty) { return; } ctx.dirty = true; schedule_frame_and_commit(); } void wl_init(void) { init_wayland(); } void wl_deinit(void) { } window wl_win_create(void) { struct window_wl *win = g_malloc0(sizeof(struct window_wl)); return win; } void wl_win_destroy(window winptr) { struct window_wl *win = (struct window_wl*)winptr; // FIXME: Dealloc everything g_free(win); } void wl_win_show(window win) { } void wl_win_hide(window win) { LOG_W("Hiding window"); ctx.cur_dim.h = 0; set_dirty(); wl_display_roundtrip(ctx.display); } void wl_display_surface(cairo_surface_t *srf, window winptr, const struct dimensions* dim) { struct window_wl *win = (struct window_wl*)winptr; ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers, dim->w, dim->h); cairo_t *c = ctx.current_buffer->cairo; cairo_save(c); cairo_set_source_surface(c, srf, 0, 0); cairo_rectangle(c, 0, 0, dim->w, dim->h); cairo_fill(c); cairo_restore(c); ctx.cur_dim = *dim; set_dirty(); wl_display_roundtrip(ctx.display); } bool wl_win_visible(window win) { return true; } cairo_t* wl_win_get_context(window winptr) { struct window_wl *win = (struct window_wl*)winptr; ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers, 500, 500); win->c_surface = ctx.current_buffer->surface; win->c_ctx = ctx.current_buffer->cairo; return win->c_ctx; } const struct screen_info* wl_get_active_screen(void) { // TODO Screen size detection static struct screen_info scr = { .w = 1920, .h = 1080, .x = 0, .y = 0, .id = 0, .mmh = 500 }; return &scr; } bool wl_is_idle(void) { return false; } bool wl_have_fullscreen_window(void) { return false; } /* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */