fwsmit f8a2ff48b3 output: remove unused win_visible function
This was used nowhere, and not even implemented for the wayland output.
2021-02-24 13:32:06 +01:00

682 lines
23 KiB
C

#define _POSIX_C_SOURCE 200112L
#include "wl.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <linux/input-event-codes.h>
#include <string.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 "protocols/idle-client-header.h"
#include "protocols/idle.h"
#include "pool-buffer.h"
#include "../log.h"
#include "../settings.h"
#include "../queues.h"
#include "../input.h"
#include "libgwater-wayland.h"
struct window_wl {
cairo_surface_t *c_surface;
cairo_t * c_ctx;
GWaterWaylandSource *esrc;
};
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 wl_seat *seat;
struct wl_list outputs;
struct wl_surface *surface;
struct dunst_output *surface_output;
struct zwlr_layer_surface_v1 *layer_surface;
struct dunst_output *layer_surface_output;
struct wl_callback *frame_callback;
struct org_kde_kwin_idle *idle_handler;
struct org_kde_kwin_idle_timeout *idle_timeout;
bool configured;
bool dirty;
bool is_idle;
bool has_idle_monitor;
struct {
struct wl_pointer *wl_pointer;
int32_t x, y;
} pointer;
struct dimensions cur_dim;
int32_t width, height;
struct pool_buffer buffers[2];
struct pool_buffer *current_buffer;
};
struct dunst_output {
uint32_t global_name;
char *name;
struct wl_output *wl_output;
struct wl_list link;
uint32_t scale;
uint32_t subpixel; // TODO do something with it
};
struct wl_ctx ctx;
static void noop() {
// This space intentionally left blank
}
void set_dirty();
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) {
//TODO
struct dunst_output *output = data;
output->subpixel = subpixel;
}
static void output_handle_scale(void *data, struct wl_output *wl_output,
int32_t factor) {
struct dunst_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 dunst_output *output = g_malloc0(sizeof(struct dunst_output));
if (output == NULL) {
LOG_E("allocation failed");
return;
}
static int number = 0;
LOG_I("New output found - id %i", number);
output->global_name = global_name;
output->wl_output = wl_output;
// TODO: Fix this
output->scale = 1;
wl_list_insert(&ctx.outputs, &output->link);
wl_output_set_user_data(wl_output, output);
wl_output_add_listener(wl_output, &output_listener, output);
number++;
}
static void destroy_output(struct dunst_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);
wl_output_destroy(output->wl_output);
free(output->name);
free(output);
}
// FIXME: Snipped touch handling
static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer,
uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
ctx.pointer.x = wl_fixed_to_int(surface_x);
ctx.pointer.y = wl_fixed_to_int(surface_y);
}
static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, uint32_t time, uint32_t button,
uint32_t button_state) {
input_handle_click(button, button_state, ctx.pointer.x, ctx.pointer.y);
}
static const struct wl_pointer_listener pointer_listener = {
.enter = noop,
.leave = noop,
.motion = pointer_handle_motion,
.button = pointer_handle_button,
.axis = noop,
};
// FIXME snipped touch listener
static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
uint32_t capabilities) {
if (ctx.pointer.wl_pointer != NULL) {
wl_pointer_release(ctx.pointer.wl_pointer);
ctx.pointer.wl_pointer = NULL;
}
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
ctx.pointer.wl_pointer = wl_seat_get_pointer(wl_seat);
wl_pointer_add_listener(ctx.pointer.wl_pointer,
&pointer_listener, ctx.seat);
LOG_I("Adding pointer");
}
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_handle_capabilities,
.name = noop,
};
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;
// not needed as it is set somewhere else
/* zwlr_layer_surface_v1_set_size(surface, width, 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_I("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 idle_start (void *data, struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) {
ctx.is_idle = true;
LOG_I("User went idle");
}
static void idle_stop (void *data, struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) {
ctx.is_idle = false;
LOG_I("User isn't idle anymore");
}
static const struct org_kde_kwin_idle_timeout_listener idle_timeout_listener = {
.idle = idle_start,
.resumed = idle_stop,
};
static void add_seat_to_idle_handler(struct wl_seat *seat) {
if (!ctx.idle_handler){
return;
}
uint32_t timeout_ms = settings.idle_threshold/1000;
ctx.idle_timeout = org_kde_kwin_idle_get_idle_timeout(ctx.idle_handler, seat, timeout_ms);
org_kde_kwin_idle_timeout_add_listener(ctx.idle_timeout, &idle_timeout_listener, 0);
ctx.has_idle_monitor = true;
}
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) {
ctx.seat = wl_registry_bind(registry, name, &wl_seat_interface, 3);
wl_seat_add_listener(ctx.seat, &seat_listener, ctx.seat);
add_seat_to_idle_handler(ctx.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, org_kde_kwin_idle_interface.name) == 0 &&
version >= ORG_KDE_KWIN_IDLE_TIMEOUT_IDLE_SINCE_VERSION) {
ctx.idle_handler = wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1);
}
}
static void handle_global_remove(void *data, struct wl_registry *registry,
uint32_t name) {
struct dunst_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 wl_init() {
wl_list_init(&ctx.outputs);
//wl_list_init(&ctx.seats); // TODO multi-seat support
ctx.display = wl_display_connect(NULL);
if (ctx.display == NULL) {
LOG_W("failed to create display");
return false;
}
ctx.registry = wl_display_get_registry(ctx.display);
wl_registry_add_listener(ctx.registry, &registry_listener, NULL);
wl_display_roundtrip(ctx.display);
if (ctx.compositor == NULL) {
LOG_W("compositor doesn't support wl_compositor");
return false;
}
if (ctx.shm == NULL) {
LOG_W("compositor doesn't support wl_shm");
return false;
}
if (ctx.layer_shell == NULL) {
LOG_W("compositor doesn't support zwlr_layer_shell_v1");
return false;
}
if (ctx.seat == NULL) {
LOG_W("no seat was found, so dunst cannot see input");
} else {
if (ctx.idle_handler == NULL) {
LOG_I("compositor doesn't support org_kde_kwin_idle_interface");
}
else if (ctx.idle_timeout == NULL) {
// something went wrong in setting the timeout
LOG_W("couldn't set idle timeout");
}
}
return true;
}
void wl_deinit() {
// We need to check if any of these are NULL, since the initialization
// could have been aborted half way through, or the compositor doesn't
// support some of these features.
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]);
// The output list is initialized at the start of init, so no need to
// check for NULL
struct dunst_output *output, *output_tmp;
wl_list_for_each_safe(output, output_tmp, &ctx.outputs, link) {
destroy_output(output);
}
if (ctx.seat) {
if (ctx.pointer.wl_pointer)
wl_pointer_release(ctx.pointer.wl_pointer);
wl_seat_release(ctx.seat);
ctx.seat = NULL;
}
if (ctx.idle_handler)
org_kde_kwin_idle_destroy(ctx.idle_handler);
if (ctx.idle_timeout)
org_kde_kwin_idle_timeout_release(ctx.idle_timeout);
if (ctx.layer_shell)
zwlr_layer_shell_v1_destroy(ctx.layer_shell);
if (ctx.compositor)
wl_compositor_destroy(ctx.compositor);
if (ctx.shm)
wl_shm_destroy(ctx.shm);
if (ctx.registry)
wl_registry_destroy(ctx.registry);
if (ctx.display)
wl_display_disconnect(ctx.display);
}
static struct dunst_output *get_configured_output() {
struct dunst_output *output;
switch (settings.f_mode){
case FOLLOW_NONE: ; // this semicolon is neccesary
int n = 0;
int target_monitor = settings.monitor;
wl_list_for_each(output, &ctx.outputs, link) {
if (n == target_monitor)
return output;
n++;
}
LOG_W("Monitor %i doesn't exist, using focused monitor", settings.monitor);
return NULL;
case FOLLOW_MOUSE:
// fallthrough
case FOLLOW_KEYBOARD:
// fallthrough
default:
return NULL;
}
}
static void schedule_frame_and_commit();
// Draw and commit a new frame.
static void send_frame() {
int scale = 1;
struct dunst_output *output = get_configured_output();
int height = ctx.cur_dim.h;
int width = ctx.cur_dim.w;
// 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;
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;
ctx.layer_surface_output = output;
ctx.surface = wl_compositor_create_surface(ctx.compositor);
wl_surface_add_listener(ctx.surface, &surface_listener, NULL);
if (settings.frame_color)
ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(
ctx.layer_shell, ctx.surface, wl_output,
settings.layer, "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 || ctx.width != width) {
struct dimensions dim = ctx.cur_dim;
// Set window size
LOG_D("Window dimensions %ix%i", dim.w, dim.h);
LOG_D("Window position %ix%i", dim.x, dim.y);
zwlr_layer_surface_v1_set_size(ctx.layer_surface,
dim.w, dim.h);
// TODO Do this only once
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!
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();
}
window wl_win_create(void) {
struct window_wl *win = g_malloc0(sizeof(struct window_wl));
win->esrc = g_water_wayland_source_new_for_display(NULL, ctx.display);
return win;
}
void wl_win_destroy(window winptr) {
struct window_wl *win = (struct window_wl*)winptr;
g_water_wayland_source_free(win->esrc);
// FIXME: Dealloc everything
g_free(win);
}
void wl_win_show(window win) {
// This is here for compatibilty with the X11 output. The window is
// already shown in wl_display_surface.
}
void wl_win_hide(window win) {
LOG_I("Wayland: 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);
}
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) {
LOG_I("Idle status queried: %i", ctx.is_idle);
// When the user doesn't have a seat, or their compositor doesn't support the idle
// protocol, we'll assume that they are not idle.
if (settings.idle_threshold == 0 || ctx.has_idle_monitor == false) {
return false;
} else {
return ctx.is_idle;
}
}
bool wl_have_fullscreen_window(void) {
return false;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */