918 lines
32 KiB
C
918 lines
32 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/wlr-foreign-toplevel-management-unstable-v1-client-header.h"
|
|
#include "protocols/wlr-foreign-toplevel-management-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"
|
|
|
|
#define MAX_TOUCHPOINTS 10
|
|
|
|
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;
|
|
struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager;
|
|
bool configured;
|
|
bool dirty;
|
|
bool is_idle;
|
|
bool has_idle_monitor;
|
|
|
|
struct {
|
|
struct wl_pointer *wl_pointer;
|
|
int32_t x, y;
|
|
} pointer;
|
|
|
|
struct {
|
|
struct wl_touch *wl_touch;
|
|
struct {
|
|
int32_t x, y;
|
|
} pts[MAX_TOUCHPOINTS];
|
|
} touch;
|
|
|
|
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
|
|
bool fullscreen;
|
|
struct zwlr_foreign_toplevel_handle_v1 *fullscreen_toplevel; // the toplevel that is fullscreened on this output
|
|
};
|
|
|
|
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 do something with the subpixel data
|
|
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;
|
|
|
|
wake_up();
|
|
}
|
|
|
|
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;
|
|
output->scale = 1;
|
|
output->fullscreen = false;
|
|
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);
|
|
}
|
|
|
|
static void touch_handle_motion(void *data, struct wl_touch *wl_touch,
|
|
uint32_t time, int32_t id,
|
|
wl_fixed_t surface_x, wl_fixed_t surface_y) {
|
|
if (id >= MAX_TOUCHPOINTS) {
|
|
return;
|
|
}
|
|
ctx.touch.pts[id].x = wl_fixed_to_int(surface_x);
|
|
ctx.touch.pts[id].y = wl_fixed_to_int(surface_y);
|
|
}
|
|
|
|
static void touch_handle_down(void *data, struct wl_touch *wl_touch,
|
|
uint32_t serial, uint32_t time, struct wl_surface *sfc, int32_t id,
|
|
wl_fixed_t surface_x, wl_fixed_t surface_y) {
|
|
if (id >= MAX_TOUCHPOINTS) {
|
|
return;
|
|
}
|
|
ctx.touch.pts[id].x = wl_fixed_to_int(surface_x);
|
|
ctx.touch.pts[id].y = wl_fixed_to_int(surface_y);
|
|
}
|
|
|
|
static void touch_handle_up(void *data, struct wl_touch *wl_touch,
|
|
uint32_t serial, uint32_t time, int32_t id) {
|
|
if (id >= MAX_TOUCHPOINTS) {
|
|
return;
|
|
}
|
|
input_handle_click(BTN_TOUCH, false,
|
|
ctx.touch.pts[id].x, ctx.touch.pts[id].y);
|
|
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
static const struct wl_touch_listener touch_listener = {
|
|
.down = touch_handle_down,
|
|
.up = touch_handle_up,
|
|
.motion = touch_handle_motion,
|
|
.frame = noop,
|
|
.cancel = noop,
|
|
};
|
|
|
|
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);
|
|
}
|
|
if (ctx.touch.wl_touch != NULL) {
|
|
wl_touch_release(ctx.touch.wl_touch);
|
|
ctx.touch.wl_touch = NULL;
|
|
}
|
|
if (capabilities & WL_SEAT_CAPABILITY_TOUCH) {
|
|
ctx.touch.wl_touch = wl_seat_get_touch(wl_seat);
|
|
wl_touch_add_listener(ctx.touch.wl_touch,
|
|
&touch_listener, ctx.seat);
|
|
}
|
|
}
|
|
|
|
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");
|
|
if (ctx.layer_surface)
|
|
zwlr_layer_surface_v1_destroy(ctx.layer_surface);
|
|
ctx.layer_surface = NULL;
|
|
|
|
if (ctx.surface)
|
|
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_D("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_D("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;
|
|
}
|
|
if (settings.idle_threshold > 0) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Warning, can return NULL
|
|
static struct dunst_output *get_configured_output() {
|
|
int n = 0;
|
|
int target_monitor = settings.monitor;
|
|
|
|
struct dunst_output *first_output = NULL, *configured_output = NULL,
|
|
*tmp_output = NULL;
|
|
wl_list_for_each(tmp_output, &ctx.outputs, link) {
|
|
if (n == 0)
|
|
first_output = tmp_output;
|
|
if (n == target_monitor)
|
|
configured_output = tmp_output;
|
|
n++;
|
|
}
|
|
|
|
// There's only 1 output, so return that
|
|
if (n == 1)
|
|
return first_output;
|
|
|
|
switch (settings.f_mode){
|
|
case FOLLOW_NONE: ; // this semicolon is neccesary
|
|
if (!configured_output) {
|
|
LOG_W("Monitor %i doesn't exist, using focused monitor", settings.monitor);
|
|
}
|
|
return configured_output;
|
|
case FOLLOW_MOUSE:
|
|
// fallthrough
|
|
case FOLLOW_KEYBOARD:
|
|
// fallthrough
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// does not do null checking
|
|
static void dunst_output_set_fullscreen(struct dunst_output *output,
|
|
struct zwlr_foreign_toplevel_handle_v1 *toplevel,
|
|
bool fullscreen) {
|
|
output->fullscreen = fullscreen;
|
|
if (fullscreen)
|
|
output->fullscreen_toplevel = toplevel;
|
|
else
|
|
output->fullscreen_toplevel = NULL;
|
|
|
|
LOG_D("Set output %i fullscreen state %i", output->global_name, fullscreen);
|
|
wake_up();
|
|
}
|
|
|
|
static void toplevel_output_leave(void *data,
|
|
struct zwlr_foreign_toplevel_handle_v1 *toplevel,
|
|
struct wl_output *output) {
|
|
zwlr_foreign_toplevel_handle_v1_set_user_data(toplevel, NULL);
|
|
}
|
|
|
|
static void toplevel_output_enter(void *data,
|
|
struct zwlr_foreign_toplevel_handle_v1 *toplevel,
|
|
struct wl_output *output) {
|
|
// FIXME toplevel can be on multiple outputs, so a list of outputs should be kept
|
|
zwlr_foreign_toplevel_handle_v1_set_user_data(toplevel, output);
|
|
}
|
|
|
|
static void toplevel_closed(void *data,
|
|
struct zwlr_foreign_toplevel_handle_v1 *toplevel) {
|
|
struct wl_output *output_wl = (struct wl_output*) data;
|
|
|
|
if (output_wl == NULL) {
|
|
return;
|
|
}
|
|
struct dunst_output *output = (struct dunst_output*) wl_output_get_user_data(output_wl);
|
|
|
|
if (output == NULL) {
|
|
return;
|
|
}
|
|
dunst_output_set_fullscreen(output, toplevel, false);
|
|
}
|
|
|
|
static void toplevel_state(void *data,
|
|
struct zwlr_foreign_toplevel_handle_v1 *toplevel,
|
|
struct wl_array *state) {
|
|
struct wl_output *output_wl = (struct wl_output*) data;
|
|
if (output_wl == NULL) {
|
|
return;
|
|
}
|
|
struct dunst_output *output = (struct dunst_output*) wl_output_get_user_data(output_wl);
|
|
if (output == NULL) {
|
|
return;
|
|
}
|
|
|
|
bool fullscreen = false;
|
|
bool activated = false;
|
|
enum zwlr_foreign_toplevel_handle_v1_state* element;
|
|
wl_array_for_each(element, state){
|
|
if (*element == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) {
|
|
fullscreen = true;
|
|
}
|
|
if (*element == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) {
|
|
activated = true;
|
|
}
|
|
}
|
|
if (fullscreen && activated) {
|
|
dunst_output_set_fullscreen(output, toplevel, true);
|
|
|
|
} else {
|
|
if (output->fullscreen_toplevel == toplevel) {
|
|
// this toplevel was fullscreen, but isn't anymore
|
|
dunst_output_set_fullscreen(output, toplevel, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const struct zwlr_foreign_toplevel_handle_v1_listener foreign_toplevel_handle_listener = {
|
|
.title = noop,
|
|
.app_id = noop,
|
|
.output_enter = toplevel_output_enter,
|
|
.output_leave = toplevel_output_leave,
|
|
.state = toplevel_state,
|
|
.done = noop,
|
|
.closed = toplevel_closed,
|
|
};
|
|
|
|
static void toplevel_created(void *data,
|
|
struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1,
|
|
struct zwlr_foreign_toplevel_handle_v1 *toplevel){
|
|
zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &foreign_toplevel_handle_listener, NULL);
|
|
}
|
|
|
|
static void toplevel_finished(void *data,
|
|
struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1){
|
|
}
|
|
|
|
static const struct zwlr_foreign_toplevel_manager_v1_listener foreign_toplevel_manager_listener = {
|
|
.toplevel = toplevel_created,
|
|
.finished = toplevel_finished,
|
|
};
|
|
|
|
static void handle_global(void *data, struct wl_registry *registry,
|
|
uint32_t name, const char *interface, uint32_t version) {
|
|
int *count = data;
|
|
if (*count == 0)
|
|
{
|
|
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);
|
|
}
|
|
} else {
|
|
if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0 &&
|
|
version >= ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN_SINCE_VERSION) {
|
|
// Only bind after the second pass to bind after binding to all the outputs.
|
|
// This is because otherwise toplevel_enter evens won't be sent.
|
|
ctx.toplevel_manager = wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, 2);
|
|
zwlr_foreign_toplevel_manager_v1_add_listener(ctx.toplevel_manager, &foreign_toplevel_manager_listener, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int count = 0;
|
|
ctx.registry = wl_display_get_registry(ctx.display);
|
|
wl_registry_add_listener(ctx.registry, ®istry_listener, &count);
|
|
wl_display_roundtrip(ctx.display);
|
|
|
|
count = 1;
|
|
// we need a second pass to let for foreign_toplevel (look there for more info)
|
|
ctx.registry = wl_display_get_registry(ctx.display);
|
|
wl_registry_add_listener(ctx.registry, ®istry_listener, &count);
|
|
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");
|
|
}
|
|
}
|
|
|
|
if (ctx.toplevel_manager == NULL) {
|
|
LOG_W("compositor doesn't support zwlr_foreign_toplevel_v1. Fullscreen detection won't work");
|
|
}
|
|
|
|
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 void schedule_frame_and_commit();
|
|
|
|
// Draw and commit a new frame.
|
|
static void send_frame() {
|
|
int scale = wl_get_scale();
|
|
|
|
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;
|
|
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,
|
|
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
|
|
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; */
|
|
int scale = wl_get_scale();
|
|
LOG_D("Buffer size (scaled) %ix%i", dim->w * scale, dim->h * scale);
|
|
ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers,
|
|
dim->w * scale, dim->h * scale);
|
|
|
|
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 * scale, dim->h * scale);
|
|
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
|
|
};
|
|
scr.dpi = wl_get_scale() * 96;
|
|
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) {
|
|
bool have_fullscreen = false;
|
|
|
|
struct dunst_output *current_output = get_configured_output();
|
|
|
|
if (!current_output) {
|
|
// Cannot detect focused output, so return true if any of the
|
|
// outputs is fullscreen. This will work even when unfocused
|
|
// outputs have fullscreen toplevels, since a toplevel has to
|
|
// be fullscreen and activate to consider an output fullscreen.
|
|
struct dunst_output *output;
|
|
wl_list_for_each(output, &ctx.outputs, link) {
|
|
have_fullscreen |= output->fullscreen;
|
|
}
|
|
} else {
|
|
have_fullscreen = current_output->fullscreen;
|
|
}
|
|
|
|
LOG_D("Fullscreen queried: %i", have_fullscreen);
|
|
return have_fullscreen;
|
|
}
|
|
|
|
int wl_get_scale(void) {
|
|
int scale = 0;
|
|
struct dunst_output *output = get_configured_output();
|
|
if (output) {
|
|
scale = output->scale;
|
|
} else {
|
|
// return the largest scale
|
|
struct dunst_output *output;
|
|
wl_list_for_each(output, &ctx.outputs, link) {
|
|
scale = MAX(output->scale, scale);
|
|
}
|
|
}
|
|
if (scale <= 0)
|
|
scale = 1;
|
|
return scale;
|
|
}
|
|
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|