#define _POSIX_C_SOURCE 200112L #include #include #include #include "xdg.h" #include "cursor.h" #include "node.h" #include "view.h" #include "output.h" static void xdg_popup_create(struct diyac_view *view, struct wlr_xdg_popup *wlr_popup); static void begin_interactive(struct diyac_view *toplevel, enum diyac_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 diyac_server *server = toplevel->server; struct wlr_surface *focused_surface = server->seat.wlr_seat->pointer_state.focused_surface; if (toplevel->xdg_toplevel->base->surface != wlr_surface_get_root_surface(focused_surface)) { /* Deny move/resize requests from unfocused clients. */ return; } server->grabbed_view = toplevel; server->seat.cursor_mode = mode; if (mode == DIYAC_CURSOR_MOVE) { server->grab_x = server->seat.cursor->x - toplevel->scene_tree->node.x; server->grab_y = server->seat.cursor->y - toplevel->scene_tree->node.y; } else { struct wlr_box geo_box; wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box); double border_x = (toplevel->scene_tree->node.x + geo_box.x) + ((edges & WLR_EDGE_RIGHT) ? geo_box.width : 0); double border_y = (toplevel->scene_tree->node.y + geo_box.y) + ((edges & WLR_EDGE_BOTTOM) ? geo_box.height : 0); server->grab_x = server->seat.cursor->x - border_x; server->grab_y = server->seat.cursor->y - border_y; server->grab_geobox = geo_box; server->grab_geobox.x += toplevel->scene_tree->node.x; server->grab_geobox.y += toplevel->scene_tree->node.y; server->resize_edges = edges; } } static void xdg_toplevel_map(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ struct diyac_view *toplevel = wl_container_of(listener, toplevel, map); wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &toplevel->original); wlr_scene_node_set_enabled(&toplevel->scene_tree->node, true); toplevel->mapped = true; wl_list_insert(&toplevel->server->views, &toplevel->link); toplevel->original.x = (toplevel->output->usable_area.width - toplevel->original.width) / 2; toplevel->original.y = (toplevel->output->usable_area.height - toplevel->original.height) / 2; if (toplevel->original.width > toplevel->output->usable_area.width) { toplevel->original.width = toplevel->output->usable_area.width; } if (toplevel->original.height > toplevel->output->usable_area.height) { toplevel->original.height = toplevel->output->usable_area.height; } diyac_view_update_geometry(toplevel, false); if (toplevel->state != DIYAC_VIEW_MINIMIZE) { diyac_focus_view(toplevel, false); } } static void xdg_toplevel_unmap(struct wl_listener *listener, void *data) { /* Called when the surface is unmapped, and should no longer be shown. */ struct diyac_view *toplevel = wl_container_of(listener, toplevel, unmap); toplevel->mapped = false; /* Reset the cursor mode if the grabbed toplevel was unmapped. */ if (toplevel == toplevel->server->grabbed_view) { diyac_reset_cursor_mode(toplevel->server); } wl_list_remove(&toplevel->link); } 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. Note that a more sophisticated compositor should check the * provided serial against a list of button press serials sent to this * client, to prevent the client from requesting this whenever they want. */ wlr_log(WLR_INFO, "Request move"); struct diyac_view *toplevel = wl_container_of(listener, toplevel, request_move); begin_interactive(toplevel, DIYAC_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. Note that a more sophisticated compositor should check the * provided serial against a list of button press serials sent to this * client, to prevent the client from requesting this whenever they want. */ wlr_log(WLR_INFO, "Request resize"); struct wlr_xdg_toplevel_resize_event *event = data; struct diyac_view *toplevel = wl_container_of(listener, toplevel, request_resize); begin_interactive(toplevel, DIYAC_CURSOR_RESIZE, event->edges); } static void xdg_toplevel_request_maximize( struct wl_listener *listener, void *data) { /* This event is raised when a client would like to maximize itself, * typically because the user clicked on the maximize button on * client-side decorations. diyac doesn't support maximization, but * to conform to xdg-shell protocol we still must send a configure. * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ struct diyac_view *toplevel = wl_container_of(listener, toplevel, request_maximize); if (!toplevel->mapped) { wlr_xdg_toplevel_set_maximized(toplevel->xdg_toplevel, false); // the view has not yet be mapped, don't maximize it wlr_log(WLR_INFO, "The view has not yet be mapped, ignore maximize request"); return; } wlr_log(WLR_INFO, "Request maximize"); diyac_reset_cursor_mode(toplevel->server); if (toplevel->state == DIYAC_VIEW_MAXIMIZE) { toplevel->state = DIYAC_VIEW_NORMAL; wlr_xdg_toplevel_set_maximized(toplevel->xdg_toplevel, false); // restore its default geometry } else { wlr_xdg_toplevel_set_maximized(toplevel->xdg_toplevel, true); toplevel->state = DIYAC_VIEW_MAXIMIZE; } diyac_view_update_geometry(toplevel, false); // wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base); } static void xdg_toplevel_request_fullscreen(struct wl_listener *listener, void *data) { struct diyac_view *toplevel = wl_container_of(listener, toplevel, request_fullscreen); wlr_log(WLR_INFO, "Cursor state %d", toplevel->server->seat.cursor_mode); if (toplevel->server->seat.cursor_mode != DIYAC_CURSOR_PASSTHROUGH) { wlr_log(WLR_INFO, "The view is under move/resize, disable fullcreen event"); wlr_xdg_toplevel_set_fullscreen(toplevel->xdg_toplevel, false); return; } if (!toplevel->mapped) { wlr_xdg_toplevel_set_fullscreen(toplevel->xdg_toplevel, false); // the view has not yet be mapped, don't maximize it wlr_log(WLR_INFO, "The view has not yet be mapped, ignore fullscreen request"); return; } wlr_log(WLR_INFO, "Request fullscreen"); diyac_reset_cursor_mode(toplevel->server); wlr_xdg_toplevel_set_fullscreen(toplevel->xdg_toplevel, false); /* if (toplevel->state == DIYAC_VIEW_FULL_SCREEN) { toplevel->state = DIYAC_VIEW_NORMAL; wlr_xdg_toplevel_set_fullscreen(toplevel->xdg_toplevel, false); // restore its default geometry } else { toplevel->state = DIYAC_VIEW_FULL_SCREEN; } */ // diyac_view_update_geometry(toplevel, false); } static void xdg_toplevel_request_minimize(struct wl_listener *listener, void *data) { struct diyac_view *toplevel = wl_container_of(listener, toplevel, request_minimize); if (toplevel->state == DIYAC_VIEW_MINIMIZE) { toplevel->state = DIYAC_VIEW_NORMAL; // restore its default geometry } else { toplevel->state = DIYAC_VIEW_MINIMIZE; } diyac_view_update_geometry(toplevel, false); } static void xdg_toplevel_destroy(struct wl_listener *listener, void *data) { /* Called when the xdg_toplevel is destroyed. */ struct diyac_view *toplevel = wl_container_of(listener, toplevel, destroy); struct diyac_view *root = diyac_get_root_view(toplevel); if (root && root->mapped) { diyac_focus_view(root, true); } else { diyac_focus_topmost_view(toplevel->server, true); } wl_list_remove(&toplevel->map.link); wl_list_remove(&toplevel->unmap.link); wl_list_remove(&toplevel->destroy.link); wl_list_remove(&toplevel->request_move.link); wl_list_remove(&toplevel->request_resize.link); wl_list_remove(&toplevel->request_maximize.link); wl_list_remove(&toplevel->request_fullscreen.link); free(toplevel); } static void handle_xdg_popup_destroy(struct wl_listener *listener, void *data) { struct diyac_popup *popup = wl_container_of(listener, popup, destroy); wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->new_popup.link); /* Usually already removed unless there was no commit at all */ if (popup->commit.notify) { wl_list_remove(&popup->commit.link); } free(popup); } static void popup_unconstrain(struct diyac_popup *popup) { struct diyac_view *view = popup->parent; struct diyac_server *server = view->server; struct wlr_output_layout *output_layout = server->output_layout; struct wlr_output *wlr_output = view->output->wlr_output; struct wlr_box geo_box = diyac_view_get_geometry(view); struct wlr_box output_toplevel_box = { .x = view->output->usable_area.x - geo_box.x, .y = view->output->usable_area.y - geo_box.y, .width = view->output->usable_area.width, .height = view->output->usable_area.height, }; wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &output_toplevel_box); } static void handle_xdg_popup_commit(struct wl_listener *listener, void *data) { struct diyac_popup *popup = wl_container_of(listener, popup, commit); struct wlr_box popup_box; wlr_xdg_surface_get_geometry(popup->wlr_popup->base, &popup_box); popup_unconstrain(popup); if (!wlr_box_empty(&popup_box)) // if (popup->wlr_popup->base->initial_commit) { struct diyac_view *view = popup->parent; // wlr_output_commit(view->output->wlr_output); /* Prevent getting called over and over again */ wl_list_remove(&popup->commit.link); popup->commit.notify = NULL; // force commit output } } static void handle_xdg_popup_new(struct wl_listener *listener, void *data) { struct diyac_popup *popup = wl_container_of(listener, popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; xdg_popup_create(popup->parent, wlr_popup); } static void xdg_popup_create(struct diyac_view *view, struct wlr_xdg_popup *wlr_popup) { struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface(wlr_popup->parent); if (!parent) { wlr_log(WLR_ERROR, "parent is not a valid XDG surface"); return; } struct diyac_popup *popup = calloc(1, sizeof(*popup)); popup->parent = view; popup->wlr_popup = wlr_popup; popup->destroy.notify = handle_xdg_popup_destroy; wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); popup->new_popup.notify = handle_xdg_popup_new; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); popup->commit.notify = handle_xdg_popup_commit; wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); /* * We must add xdg popups to the scene graph so they get rendered. The * wlroots scene graph provides a helper for this, but to use it we must * provide the proper parent scene node of the xdg popup. To enable * this, we always set the user data field of xdg_surfaces to the * corresponding scene node. * * xdg-popups live in server->xdg_popup_tree so that they can be * rendered above always-on-top windows */ struct wlr_scene_tree *parent_tree = NULL; if (parent->role == WLR_XDG_SURFACE_ROLE_POPUP) { parent_tree = parent->surface->data; } else { parent_tree = view->server->xdg_popup_tree; struct wlr_box box = diyac_view_get_geometry(view); wlr_scene_node_set_position(&view->server->xdg_popup_tree->node, box.x, box.y); } struct wlr_scene_tree *tree = wlr_scene_xdg_surface_create(parent_tree, wlr_popup->base); wlr_popup->base->surface->data = tree; // wlr_scene_node_set_enabled(&parent_tree->node, false); diyac_node_descriptor_create(wlr_popup->base->surface->data, DIYAC_NODE_XDG_POPUP, view); } static void xdg_set_appid_notify(struct wl_listener *listener, void *data) { struct diyac_view *xdg_toplevel_view = wl_container_of(listener, xdg_toplevel_view, set_app_id); wlr_log(WLR_INFO, "set application id"); } static void xdg_new_popup_notify(struct wl_listener *listener, void *data) { struct diyac_view *view = wl_container_of(listener, view, new_popup); struct wlr_xdg_popup *wlr_popup = data; xdg_popup_create(view, wlr_popup); } void diyac_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 diyac_server *server = wl_container_of(listener, server, new_xdg_surface); struct wlr_xdg_surface *xdg_surface = data; /* We must add xdg popups to the scene graph so they get rendered. The * wlroots scene graph provides a helper for this, but to use it we must * provide the proper parent scene node of the xdg popup. To enable this, * we always set the user data field of xdg_surfaces to the corresponding * scene node. */ if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { /*struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent); assert(parent != NULL); struct wlr_scene_tree *parent_tree = parent->data; xdg_surface->data = wlr_scene_xdg_surface_create( parent_tree, xdg_surface); return;*/ wlr_log(WLR_INFO, "diyac_new_xdg_surface: Creating new dialog using view popup"); return; } assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); wlr_log(WLR_INFO, "diyac_new_xdg_surface: Creating new application windows"); wlr_xdg_surface_ping(xdg_surface); /* Allocate a diyac_view for this surface */ struct diyac_view *toplevel = calloc(1, sizeof(*toplevel)); toplevel->state = DIYAC_VIEW_NORMAL; toplevel->server = server; toplevel->xdg_toplevel = xdg_surface->toplevel; toplevel->xdg_surface = xdg_surface; toplevel->mapped = false; toplevel->output = diyac_output_from_cursor(server); toplevel->scene_tree = wlr_scene_xdg_surface_create( toplevel->server->view_tree, toplevel->xdg_toplevel->base); xdg_surface->data = toplevel; if (toplevel->output) { wlr_fractional_scale_v1_notify_scale(xdg_surface->surface, toplevel->output->wlr_output->scale); } wlr_scene_node_set_enabled(&toplevel->scene_tree->node, false); diyac_node_descriptor_create(&toplevel->scene_tree->node, DIYAC_NODE_VIEW, toplevel); /* Listen to the various events it can emit */ toplevel->map.notify = xdg_toplevel_map; wl_signal_add(&xdg_surface->surface->events.map, &toplevel->map); toplevel->unmap.notify = xdg_toplevel_unmap; wl_signal_add(&xdg_surface->surface->events.unmap, &toplevel->unmap); toplevel->destroy.notify = xdg_toplevel_destroy; wl_signal_add(&xdg_surface->events.destroy, &toplevel->destroy); /* cotd */ struct wlr_xdg_toplevel *xdg_toplevel = xdg_surface->toplevel; toplevel->request_move.notify = xdg_toplevel_request_move; wl_signal_add(&xdg_toplevel->events.request_move, &toplevel->request_move); toplevel->request_resize.notify = xdg_toplevel_request_resize; wl_signal_add(&xdg_toplevel->events.request_resize, &toplevel->request_resize); toplevel->request_minimize.notify = xdg_toplevel_request_minimize; wl_signal_add(&xdg_toplevel->events.request_minimize, &toplevel->request_minimize); toplevel->request_maximize.notify = xdg_toplevel_request_maximize; wl_signal_add(&xdg_toplevel->events.request_maximize, &toplevel->request_maximize); toplevel->request_fullscreen.notify = xdg_toplevel_request_fullscreen; wl_signal_add(&xdg_toplevel->events.request_fullscreen, &toplevel->request_fullscreen); toplevel->new_popup.notify = xdg_new_popup_notify; wl_signal_add(&xdg_surface->events.new_popup, &toplevel->new_popup); toplevel->set_app_id.notify = xdg_set_appid_notify; wl_signal_add(&xdg_toplevel->events.set_app_id, &toplevel->set_app_id); }