mirror of
https://github.com/wizbright/waybox.git
synced 2025-10-31 22:25:28 -04:00
199 lines
7.3 KiB
C
199 lines
7.3 KiB
C
#include "waybox/xdg_shell.h"
|
|
|
|
void focus_view(struct wb_view *view, struct wlr_surface *surface) {
|
|
/* Note: this function only deals with keyboard focus. */
|
|
if (view == NULL) {
|
|
return;
|
|
}
|
|
struct wb_server *server = view->server;
|
|
struct wlr_seat *seat = server->seat->seat;
|
|
struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface;
|
|
if (prev_surface == surface) {
|
|
/* Don't re-focus an already focused surface. */
|
|
return;
|
|
}
|
|
if (prev_surface) {
|
|
/*
|
|
* Deactivate the previously focused surface. This lets the client know
|
|
* it no longer has focus and the client will repaint accordingly, e.g.
|
|
* stop displaying a caret.
|
|
*/
|
|
struct wlr_xdg_surface *previous = wlr_xdg_surface_from_wlr_surface(
|
|
seat->keyboard_state.focused_surface);
|
|
wlr_xdg_toplevel_set_activated(previous, false);
|
|
}
|
|
struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat);
|
|
/* Move the view to the front */
|
|
wl_list_remove(&view->link);
|
|
wl_list_insert(&server->views, &view->link);
|
|
/* Activate the new surface */
|
|
wlr_xdg_toplevel_set_activated(view->xdg_surface, true);
|
|
/*
|
|
* Tell the seat to have the keyboard enter this surface. wlroots will keep
|
|
* track of this and automatically send key events to the appropriate
|
|
* clients without additional work on your part.
|
|
*/
|
|
wlr_seat_keyboard_notify_enter(seat, view->xdg_surface->surface,
|
|
keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers);
|
|
}
|
|
|
|
static void xdg_surface_map(struct wl_listener *listener, void *data) {
|
|
/* Called when the surface is mapped, or ready to display on-screen. */
|
|
struct wb_view *view = wl_container_of(listener, view, map);
|
|
view->mapped = true;
|
|
focus_view(view, view->xdg_surface->surface);
|
|
}
|
|
|
|
static void xdg_surface_unmap(struct wl_listener *listener, void *data) {
|
|
/* Called when the surface is unmapped, and should no longer be shown. */
|
|
struct wb_view *view = wl_container_of(listener, view, unmap);
|
|
view->mapped = false;
|
|
|
|
struct wb_view *current_view = (struct wb_view *) view->server->views.next;
|
|
struct wb_view *next_view = (struct wb_view *) current_view->link.next;
|
|
|
|
/* If the current view is mapped, focus it. */
|
|
if (current_view->mapped) {
|
|
focus_view(current_view, current_view->xdg_surface->surface);
|
|
}
|
|
/* Otherwise, focus the next view, if any. */
|
|
else if (next_view->xdg_surface->surface && wlr_surface_is_xdg_surface(next_view->xdg_surface->surface)) {
|
|
focus_view(next_view, next_view->xdg_surface->surface);
|
|
}
|
|
}
|
|
|
|
static void xdg_surface_destroy(struct wl_listener *listener, void *data) {
|
|
/* Called when the surface is destroyed and should never be shown again. */
|
|
struct wb_view *view = wl_container_of(listener, view, destroy);
|
|
wl_list_remove(&view->link);
|
|
free(view);
|
|
}
|
|
|
|
static void begin_interactive(struct wb_view *view,
|
|
enum wb_cursor_mode mode, uint32_t edges) {
|
|
/* This function sets up an interactive move or resize operation, where the
|
|
* compositor stops propagating pointer events to clients and instead
|
|
* consumes them itself, to move or resize windows. */
|
|
struct wb_server *server = view->server;
|
|
struct wlr_surface *focused_surface =
|
|
server->seat->seat->pointer_state.focused_surface;
|
|
if (view->xdg_surface->surface != focused_surface) {
|
|
/* Deny move/resize requests from unfocused clients. */
|
|
return;
|
|
}
|
|
server->grabbed_view = view;
|
|
server->cursor->cursor_mode = mode;
|
|
struct wlr_box geo_box;
|
|
wlr_xdg_surface_get_geometry(view->xdg_surface, &geo_box);
|
|
if (mode == WB_CURSOR_MOVE) {
|
|
server->grab_x = server->cursor->cursor->x - view->x;
|
|
server->grab_y = server->cursor->cursor->y - view->y;
|
|
} else {
|
|
server->grab_x = server->cursor->cursor->x + geo_box.x;
|
|
server->grab_y = server->cursor->cursor->y + geo_box.y;
|
|
}
|
|
server->grab_width = geo_box.width;
|
|
server->grab_height = geo_box.height;
|
|
server->resize_edges = edges;
|
|
}
|
|
|
|
static void xdg_toplevel_request_move(
|
|
struct wl_listener *listener, void *data) {
|
|
/* This event is raised when a client would like to begin an interactive
|
|
* move, typically because the user clicked on their client-side
|
|
* decorations. */
|
|
struct wb_view *view = wl_container_of(listener, view, request_move);
|
|
begin_interactive(view, WB_CURSOR_MOVE, 0);
|
|
}
|
|
|
|
static void xdg_toplevel_request_resize(
|
|
struct wl_listener *listener, void *data) {
|
|
/* This event is raised when a client would like to begin an interactive
|
|
* resize, typically because the user clicked on their client-side
|
|
* decorations. */
|
|
struct wlr_xdg_toplevel_resize_event *event = data;
|
|
struct wb_view *view = wl_container_of(listener, view, request_resize);
|
|
begin_interactive(view, WB_CURSOR_RESIZE, event->edges);
|
|
}
|
|
|
|
static void handle_new_xdg_surface(struct wl_listener *listener, void *data) {
|
|
/* This event is raised when wlr_xdg_shell receives a new xdg surface from a
|
|
* client, either a toplevel (application window) or popup. */
|
|
struct wb_server *server =
|
|
wl_container_of(listener, server, new_xdg_surface);
|
|
struct wlr_xdg_surface *xdg_surface = data;
|
|
if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
|
|
return;
|
|
}
|
|
|
|
/* Allocate a wb_view for this surface */
|
|
struct wb_view *view =
|
|
calloc(1, sizeof(struct wb_view));
|
|
view->server = server;
|
|
view->xdg_surface = xdg_surface;
|
|
|
|
/* Listen to the various events it can emit */
|
|
view->map.notify = xdg_surface_map;
|
|
wl_signal_add(&xdg_surface->events.map, &view->map);
|
|
view->unmap.notify = xdg_surface_unmap;
|
|
wl_signal_add(&xdg_surface->events.unmap, &view->unmap);
|
|
view->destroy.notify = xdg_surface_destroy;
|
|
wl_signal_add(&xdg_surface->events.destroy, &view->destroy);
|
|
|
|
struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel;
|
|
view->request_move.notify = xdg_toplevel_request_move;
|
|
wl_signal_add(&toplevel->events.request_move, &view->request_move);
|
|
view->request_resize.notify = xdg_toplevel_request_resize;
|
|
wl_signal_add(&toplevel->events.request_resize, &view->request_resize);
|
|
|
|
/* Add it to the list of views. */
|
|
wl_list_insert(&server->views, &view->link);
|
|
}
|
|
|
|
bool view_at(struct wb_view *view,
|
|
double lx, double ly, struct wlr_surface **surface,
|
|
double *sx, double *sy) {
|
|
/*
|
|
* XDG toplevels may have nested surfaces, such as popup windows for context
|
|
* menus or tooltips. This function tests if any of those are underneath the
|
|
* coordinates lx and ly (in output Layout Coordinates). If so, it sets the
|
|
* surface pointer to that wlr_surface and the sx and sy coordinates to the
|
|
* coordinates relative to that surface's top-left corner.
|
|
*/
|
|
double view_sx = lx - view->x;
|
|
double view_sy = ly - view->y;
|
|
|
|
double _sx, _sy;
|
|
struct wlr_surface *_surface = NULL;
|
|
_surface = wlr_xdg_surface_surface_at(
|
|
view->xdg_surface, view_sx, view_sy, &_sx, &_sy);
|
|
|
|
if (_surface != NULL) {
|
|
*sx = _sx;
|
|
*sy = _sy;
|
|
*surface = _surface;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
struct wb_view *desktop_view_at(
|
|
struct wb_server *server, double lx, double ly,
|
|
struct wlr_surface **surface, double *sx, double *sy) {
|
|
/* This iterates over all of our surfaces and attempts to find one under the
|
|
* cursor. This relies on server->views being ordered from top-to-bottom. */
|
|
struct wb_view *view;
|
|
wl_list_for_each(view, &server->views, link) {
|
|
if (view_at(view, lx, ly, surface, sx, sy)) {
|
|
return view;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void init_xdg_shell(struct wb_server *server) {
|
|
server->xdg_shell = wlr_xdg_shell_create(server->wl_display);
|
|
server->new_xdg_surface.notify = handle_new_xdg_surface;
|
|
wl_signal_add(&server->xdg_shell->events.new_surface, &server->new_xdg_surface);
|
|
}
|