diff --git a/heart/example/main.c b/heart/example/main.c index 370b5c2..33278b7 100644 --- a/heart/example/main.c +++ b/heart/example/main.c @@ -75,7 +75,10 @@ int main(int argc, char *argv[]) { struct hrt_server server; - if(!hrt_server_init(&server, &output_callbacks, &seat_callbacks, &view_callbacks, WLR_DEBUG)) { + if (!hrt_server_init(&server, &output_callbacks, &seat_callbacks, + &view_callbacks, + NULL, + WLR_DEBUG)) { return 1; } diff --git a/heart/include/hrt/hrt_layer_shell.h b/heart/include/hrt/hrt_layer_shell.h new file mode 100644 index 0000000..2be9452 --- /dev/null +++ b/heart/include/hrt/hrt_layer_shell.h @@ -0,0 +1,59 @@ +#ifndef HRT_LAYER_SHELL +#define HRT_LAYER_SHELL + +#include +#include +#include + +#include + +struct hrt_output; + +struct hrt_layer_shell_surface { + struct wlr_layer_surface_v1 *layer_surface; + struct wlr_scene_layer_surface_v1 *scene_layer; + struct hrt_scene_output *output; + bool mapped; + struct { + struct wl_listener commit; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener new_popup; + struct wl_listener scene_destroy; + } events; +}; + +typedef void (*layer_shell_event_handler)(struct hrt_layer_shell_surface *); + +struct hrt_layer_shell_callbacks { + layer_shell_event_handler new_layer_surface; +}; + +struct hrt_layer_shell_surface * +hrt_layer_shell_surface_create(struct wlr_layer_surface_v1 *surface); + +/** + * Destroy a partially created hrt_layer_shell-surface object + */ +void +hrt_layer_shell_surface_abort(struct hrt_layer_shell_surface *surface); + +struct hrt_output * +hrt_layer_surface_output(struct hrt_layer_shell_surface *layer_shell); + +void hrt_layer_shell_surface_set_output( + struct hrt_layer_shell_surface *layer_shell, struct hrt_output *output); + +/** + * Place a freshly-initialized surface in an output. Should only be called once during + * intial placement. + */ +void hrt_layer_shell_surface_place(struct hrt_layer_shell_surface *surface, + struct hrt_scene_output *output); + +/** + * Finish initializing the layer shell object + */ +void hrt_layer_shell_finish_init(struct hrt_layer_shell_surface *surface); + +#endif diff --git a/heart/include/hrt/hrt_scene.h b/heart/include/hrt/hrt_scene.h index f9ded90..28dc90a 100644 --- a/heart/include/hrt/hrt_scene.h +++ b/heart/include/hrt/hrt_scene.h @@ -3,10 +3,12 @@ #include #include -#include #include #include #include +#include + +struct hrt_output; struct hrt_scene_root { struct wlr_scene_tree *background; @@ -17,7 +19,7 @@ struct hrt_scene_root { struct wlr_scene_tree *overlay; // Should we store the outputs and groups associated with this? struct { - struct wl_listener scene_destroy; + struct wl_listener scene_destroy; } listeners; }; @@ -27,6 +29,7 @@ struct hrt_scene_output { struct wlr_scene_tree *top; struct wlr_scene_tree *overlay; + struct hrt_output *output; }; struct hrt_scene_group { @@ -49,6 +52,10 @@ struct hrt_scene_output *hrt_scene_output_create(struct hrt_scene_root *scene); void hrt_scene_output_destroy(struct hrt_scene_output *output); +struct wlr_scene_tree *hrt_scene_output_get_layer( + struct hrt_scene_output *output, enum zwlr_layer_shell_v1_layer layer_type); + + struct hrt_scene_group *hrt_scene_group_create(struct hrt_scene_root *parent); void hrt_scene_group_destroy(struct hrt_scene_group *group); diff --git a/heart/include/hrt/hrt_server.h b/heart/include/hrt/hrt_server.h index e5c6f72..ca48cca 100644 --- a/heart/include/hrt/hrt_server.h +++ b/heart/include/hrt/hrt_server.h @@ -1,6 +1,7 @@ #ifndef HRT_HRT_SERVER_H #define HRT_HRT_SERVER_H +#include "hrt/hrt_layer_shell.h" #include "wlr/backend/session.h" #include @@ -19,7 +20,8 @@ struct hrt_server { struct wl_display *wl_display; struct wlr_backend *backend; - struct wl_listener backend_destroy; + struct wlr_backend *headless_backend; + struct wlr_session *session; struct wlr_renderer *renderer; struct wlr_compositor *compositor; @@ -33,23 +35,36 @@ struct hrt_server { struct wl_listener output_layout_changed; struct wl_listener output_manager_apply; struct wl_listener output_manager_test; - struct wl_listener output_manager_destroy; struct hrt_seat seat; + struct hrt_output *fallback_output; struct wlr_xdg_shell *xdg_shell; struct wl_listener new_xdg_toplevel; struct wl_listener new_xdg_popup; + struct wlr_layer_shell_v1 *layer_shell; + struct wl_listener new_layer_shell; + + struct { + struct wl_listener backend; + struct wl_listener headless; + struct wl_listener output_manager; + struct wl_listener layer_shell; + } destroy_listener; + const struct hrt_output_callbacks *output_callback; const struct hrt_view_callbacks *view_callbacks; + const struct hrt_layer_shell_callbacks *layer_shell_callbacks; }; -bool hrt_server_init(struct hrt_server *server, - const struct hrt_output_callbacks *output_callbacks, - const struct hrt_seat_callbacks *seat_callbacks, - const struct hrt_view_callbacks *view_callbacks, - enum wlr_log_importance log_level); +bool hrt_server_init( + struct hrt_server *server, + const struct hrt_output_callbacks *output_callbacks, + const struct hrt_seat_callbacks *seat_callbacks, + const struct hrt_view_callbacks *view_callbacks, + const struct hrt_layer_shell_callbacks *layer_shell_callbacks, + enum wlr_log_importance log_level); bool hrt_server_start(struct hrt_server *server); diff --git a/heart/include/layer_shell_impl.h b/heart/include/layer_shell_impl.h new file mode 100644 index 0000000..13f0f44 --- /dev/null +++ b/heart/include/layer_shell_impl.h @@ -0,0 +1,9 @@ +#ifndef LAYER_SHELL_IMPL +#define LAYER_SHELL_IMPL + +#include "hrt/hrt_server.h" +#include + +bool hrt_layer_shell_init(struct hrt_server *server); + +#endif diff --git a/heart/include/output_impl.h b/heart/include/output_impl.h index 96c7c92..68e1093 100644 --- a/heart/include/output_impl.h +++ b/heart/include/output_impl.h @@ -1,7 +1,12 @@ #pragma once +#include + #include "hrt/hrt_server.h" bool hrt_output_init(struct hrt_server *server, const struct hrt_output_callbacks *callbacks); void hrt_output_destroy(struct hrt_server *server); + +struct hrt_output *hrt_output_create(struct hrt_server *server, + struct wlr_output *wlr_output); diff --git a/heart/protocols/meson.build b/heart/protocols/meson.build index 5c5ae58..b749037 100644 --- a/heart/protocols/meson.build +++ b/heart/protocols/meson.build @@ -19,6 +19,7 @@ endif protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wlr_protocol_dir, 'wlr-output-management-unstable-v1.xml'], + [wlr_protocol_dir, 'wlr-layer-shell-unstable-v1.xml'], ] wl_protos_src = [] diff --git a/heart/protocols/wlr-layer-shell-unstable-v1.xml b/heart/protocols/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..d62fd51 --- /dev/null +++ b/heart/protocols/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,390 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + diff --git a/heart/src/layer_shell.c b/heart/src/layer_shell.c new file mode 100644 index 0000000..87adab6 --- /dev/null +++ b/heart/src/layer_shell.c @@ -0,0 +1,232 @@ +#include "hrt/hrt_layer_shell.h" +#include "hrt/hrt_output.h" +#include "hrt/hrt_scene.h" +#include "wlr/util/box.h" +#include "wlr/util/log.h" + +#include +#include + +#include +#include +#include + +#include + +struct hrt_layer_shell_surface * +hrt_layer_shell_surface_create(struct wlr_layer_surface_v1 *surface) { + struct hrt_layer_shell_surface *shell_surface = + calloc(1, sizeof(struct hrt_layer_shell_surface)); + + shell_surface->layer_surface = surface; + return shell_surface; +} + +void hrt_layer_shell_surface_abort(struct hrt_layer_shell_surface *surface) { + wlr_layer_surface_v1_destroy(surface->layer_surface); + free(surface); +} + +static void handle_new_layer_surface(struct wl_listener *listener, void *data) { + wlr_log(WLR_DEBUG, "New layer surface"); + struct wlr_layer_surface_v1 *layer_surface = data; + struct hrt_server *server = + wl_container_of(listener, server, new_layer_shell); + struct hrt_layer_shell_surface *surface = + hrt_layer_shell_surface_create(layer_surface); + + // The rest of the initialization should happen in the callback: + server->layer_shell_callbacks->new_layer_surface(surface); +} + +void hrt_layer_shell_surface_place(struct hrt_layer_shell_surface *surface, + struct hrt_scene_output *output) { + enum zwlr_layer_shell_v1_layer layer_type = + surface->layer_surface->pending.layer; + struct wlr_scene_tree *output_layer = + hrt_scene_output_get_layer(output, layer_type); + assert(output_layer); + + wlr_log(WLR_DEBUG, "layer %d", layer_type); + surface->scene_layer = + wlr_scene_layer_surface_v1_create(output_layer, surface->layer_surface); + surface->scene_layer->tree->node.data = surface; + wlr_log(WLR_DEBUG, "Surface created"); + surface->output = output; +} + +void hrt_layer_shell_surface_set_output( + struct hrt_layer_shell_surface *layer_shell, struct hrt_output *output) { + layer_shell->layer_surface->output = output->wlr_output; +} + +static void hrt_layer_shell_destroy(struct wl_listener *listener, void *data) { + struct hrt_server *server = + wl_container_of(listener, server, destroy_listener.layer_shell); + + wl_list_remove(&server->new_layer_shell.link); + wl_list_remove(&server->destroy_listener.layer_shell.link); +} + +bool hrt_layer_shell_init(struct hrt_server *server) { + struct wlr_layer_shell_v1 *shell = + wlr_layer_shell_v1_create(server->wl_display, 5); + if (!shell) { + return false; + } + server->layer_shell = shell; + + server->new_layer_shell.notify = handle_new_layer_surface; + wl_signal_add(&shell->events.new_surface, &server->new_layer_shell); + server->destroy_listener.layer_shell.notify = hrt_layer_shell_destroy; + wl_signal_add(&shell->events.destroy, + &server->destroy_listener.layer_shell); + + return true; +} + +static void arrange_surface(const struct wlr_box *full_area, + struct wlr_box *usable_area, + struct wlr_scene_tree *tree, bool exclusive) { + struct wlr_scene_node *node; + wl_list_for_each(node, &tree->children, link) { + // this should work assuming that the only children of the given tree are + // from layer shell objects: + struct hrt_layer_shell_surface *surface = node->data; + // surface could be null during destruction + if (!surface) { + wlr_log(WLR_DEBUG, "No surface"); + continue; + } + + if (!surface->scene_layer->layer_surface->initialized) { + wlr_log(WLR_DEBUG, "Not initailzed"); + continue; + } + + if ((surface->scene_layer->layer_surface->current.exclusive_zone > 0) != + exclusive) { + wlr_log(WLR_DEBUG, "exclusive"); + continue; + } + + wlr_log(WLR_DEBUG, "Sending configure"); + wlr_scene_layer_surface_v1_configure(surface->scene_layer, full_area, + usable_area); + } +} + +static void arrange_layers(struct hrt_output *output, + struct hrt_scene_output *scene_output) { + struct wlr_box usable_area = {0}; + hrt_output_position(output, &usable_area.x, &usable_area.y); + wlr_output_effective_resolution(output->wlr_output, &usable_area.width, + &usable_area.height); + const struct wlr_box full_area = usable_area; + + arrange_surface(&full_area, &usable_area, scene_output->overlay, true); + arrange_surface(&full_area, &usable_area, scene_output->top, true); + arrange_surface(&full_area, &usable_area, scene_output->bottom, true); + arrange_surface(&full_area, &usable_area, scene_output->background, true); + + arrange_surface(&full_area, &usable_area, scene_output->overlay, false); + arrange_surface(&full_area, &usable_area, scene_output->top, false); + arrange_surface(&full_area, &usable_area, scene_output->bottom, false); + arrange_surface(&full_area, &usable_area, scene_output->background, false); + + if (!wlr_box_equal(&usable_area, &output->usable_area)) { + wlr_log(WLR_DEBUG, "Usable area changed, rearranging output"); + output->usable_area = usable_area; + // TODO: this reconfigures all outputs, we can do way less work: + output->server->output_callback->output_layout_changed(); + } else { + // arrange_popups(root->layers.popup); + } +} + +static void handle_surface_commit(struct wl_listener *listener, void *data) { + struct hrt_layer_shell_surface *surface = + wl_container_of(listener, surface, events.commit); + wlr_log(WLR_DEBUG, "layer shell surface commit"); + struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface; + uint32_t committed = layer_surface->current.committed; + if (layer_surface->initialized && + committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { + enum zwlr_layer_shell_v1_layer layer_type = + layer_surface->current.layer; + struct wlr_scene_tree *output_layer = + hrt_scene_output_get_layer(surface->output, layer_type); + wlr_scene_node_reparent(&surface->scene_layer->tree->node, + output_layer); + } + + if (layer_surface->initial_commit || committed || + layer_surface->surface->mapped != surface->mapped) { + surface->mapped = layer_surface->surface->mapped; + struct wlr_output *output = surface->layer_surface->output; + struct hrt_output *hrt_output = output->data; + arrange_layers(hrt_output, surface->output); + } +} + +static void handle_map(struct wl_listener *listener, void *data) { + struct hrt_layer_shell_surface *surface = + wl_container_of(listener, surface, events.map); + wlr_log(WLR_DEBUG, "layer shell surface mapped"); +} + +static void handle_unmap(struct wl_listener *listener, void *data) { + struct hrt_layer_shell_surface *surface = + wl_container_of(listener, surface, events.unmap); + wlr_log(WLR_DEBUG, "layer shell surface unmapped"); +} + +static void handle_new_popup(struct wl_listener *listener, void *data) { + struct hrt_layer_shell_surface *surface = + wl_container_of(listener, surface, events.new_popup); + wlr_log(WLR_DEBUG, "layer shell surface popup"); +} + +static void handle_node_destroy(struct wl_listener *listener, void *data) { + wlr_log(WLR_DEBUG, "layer shell surface node destroy"); + struct hrt_layer_shell_surface *surface = + wl_container_of(listener, surface, events.scene_destroy); + + surface->scene_layer->tree->node.data = NULL; + if (surface->output) { + struct wlr_output *output = surface->layer_surface->output; + struct hrt_output *hrt_output = output->data; + arrange_layers(hrt_output, surface->output); + } + + wl_list_remove(&surface->events.scene_destroy.link); + wl_list_remove(&surface->events.new_popup.link); + wl_list_remove(&surface->events.unmap.link); + wl_list_remove(&surface->events.map.link); + wl_list_remove(&surface->events.commit.link); + + free(surface); +} + +void hrt_layer_shell_finish_init(struct hrt_layer_shell_surface *surface) { + struct wlr_layer_surface_v1 *const layer_surface = surface->layer_surface; + surface->events.commit.notify = handle_surface_commit; + wl_signal_add(&layer_surface->surface->events.commit, + &surface->events.commit); + surface->events.map.notify = handle_map; + wl_signal_add(&layer_surface->surface->events.map, &surface->events.map); + surface->events.unmap.notify = handle_unmap; + wl_signal_add(&layer_surface->surface->events.unmap, + &surface->events.unmap); + surface->events.new_popup.notify = handle_new_popup; + wl_signal_add(&layer_surface->events.new_popup, &surface->events.new_popup); + + surface->events.scene_destroy.notify = handle_node_destroy; + wl_signal_add(&surface->scene_layer->tree->node.events.destroy, + &surface->events.scene_destroy); +} + +struct hrt_output * +hrt_layer_surface_output(struct hrt_layer_shell_surface *layer_shell) { + return (struct hrt_output *)layer_shell->layer_surface->data; +} diff --git a/heart/src/meson.build b/heart/src/meson.build index 6c97625..d1f2d77 100644 --- a/heart/src/meson.build +++ b/heart/src/meson.build @@ -9,10 +9,11 @@ hrt_source_files += files( 'server.c', 'view.c', 'xdg_shell.c', - ) + 'layer_shell.c', +) if get_option('debug_utils') hrt_source_files += files( 'debug/debug.c' ) -endif \ No newline at end of file +endif diff --git a/heart/src/output.c b/heart/src/output.c index 7b2bdd4..4624f21 100644 --- a/heart/src/output.c +++ b/heart/src/output.c @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -32,10 +33,13 @@ static void handle_frame_notify(struct wl_listener *listener, void *data) { } static void handle_output_destroy(struct wl_listener *listener, void *data) { - wlr_log(WLR_DEBUG, "Output destroyed"); struct hrt_output *output = wl_container_of(listener, output, destroy); struct hrt_server *server = output->server; - server->output_callback->output_removed(output); + wlr_log(WLR_DEBUG, "Output destroyed %s", output->wlr_output->name); + + if (!wlr_output_is_headless(output->wlr_output)) { + server->output_callback->output_removed(output); + } wl_list_remove(&output->frame.link); wl_list_remove(&output->request_state.link); @@ -53,17 +57,12 @@ static float float_rand() { return (float)(rand() / (double)RAND_MAX); /* [0, 1.0] */ } -static struct hrt_output *hrt_output_create(struct hrt_server *server, - struct wlr_output *wlr_output) { +struct hrt_output *hrt_output_create(struct hrt_server *server, + struct wlr_output *wlr_output) { struct hrt_output *output = calloc(1, sizeof(struct hrt_output)); output->wlr_output = wlr_output; output->server = server; - output->frame.notify = handle_frame_notify; - wl_signal_add(&wlr_output->events.frame, &output->frame); - output->request_state.notify = handle_request_state; - wl_signal_add(&wlr_output->events.request_state, &output->request_state); - // temp background color: // {0.730473, 0.554736, 0.665036, 1.000000} is really pretty. output->color[0] = float_rand(); @@ -74,19 +73,25 @@ static struct hrt_output *hrt_output_create(struct hrt_server *server, printf("Output color: {%f, %f, %f, %f}\n", output->color[0], output->color[1], output->color[2], output->color[3]); + wlr_output->data = output; + return output; } static void handle_new_output(struct wl_listener *listener, void *data) { - wlr_log(WLR_DEBUG, "New output detected"); struct hrt_server *server = wl_container_of(listener, server, new_output); struct wlr_output *wlr_output = data; - // Initialize and set the data pointer so it's available in any events that + if (server->fallback_output->wlr_output == wlr_output) { + return; + } + + // Initialize the hrt_output here so it's available in any events that // are triggered in the subsequent code: struct hrt_output *output = hrt_output_create(server, wlr_output); - wlr_output->data = output; + + wlr_log(WLR_DEBUG, "New output detected: %s", wlr_output->name); wlr_output_init_render(wlr_output, server->allocator, server->renderer); @@ -112,6 +117,12 @@ static void handle_new_output(struct wl_listener *listener, void *data) { wlr_scene_output_layout_add_output(server->scene_layout, l_output, scene_output); + // Setup the events here so they don't get assigned to headless outputs: + output->frame.notify = handle_frame_notify; + wl_signal_add(&wlr_output->events.frame, &output->frame); + output->request_state.notify = handle_request_state; + wl_signal_add(&wlr_output->events.request_state, &output->request_state); + output->destroy.notify = handle_output_destroy; wl_signal_add(&wlr_output->events.destroy, &output->destroy); @@ -123,11 +134,11 @@ static void handle_output_manager_destroy(struct wl_listener *listener, wlr_log(WLR_DEBUG, "Output Manager destroyed"); struct hrt_server *server = - wl_container_of(listener, server, output_manager_destroy); + wl_container_of(listener, server, destroy_listener.output_manager); wl_list_remove(&server->output_manager_apply.link); wl_list_remove(&server->output_manager_test.link); - wl_list_remove(&server->output_manager_destroy.link); + wl_list_remove(&server->destroy_listener.output_manager.link); } static void handle_output_manager_apply(struct wl_listener *listener, @@ -187,9 +198,9 @@ bool hrt_output_init(struct hrt_server *server, server->output_manager_test.notify = handle_output_manager_test; wl_signal_add(&server->output_manager->events.apply, &server->output_manager_test); - server->output_manager_destroy.notify = handle_output_manager_destroy; + server->destroy_listener.output_manager.notify = handle_output_manager_destroy; wl_signal_add(&server->output_manager->events.destroy, - &server->output_manager_destroy); + &server->destroy_listener.output_manager); // temporary random seed: srand(time(0)); diff --git a/heart/src/scene.c b/heart/src/scene.c index d7588c4..0fa0619 100644 --- a/heart/src/scene.c +++ b/heart/src/scene.c @@ -65,6 +65,23 @@ void hrt_scene_output_destroy(struct hrt_scene_output *output) { free(output); } +struct wlr_scene_tree * +hrt_scene_output_get_layer(struct hrt_scene_output *output, + enum zwlr_layer_shell_v1_layer layer_type) { + switch (layer_type) { + case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: + return output->background; + case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: + return output->bottom; + case ZWLR_LAYER_SHELL_V1_LAYER_TOP: + return output->bottom; + case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: + return output->overlay; + default: + return NULL; + } +} + struct hrt_scene_group *hrt_scene_group_create(struct hrt_scene_root *parent) { struct hrt_scene_group *layers = calloc(1, sizeof(*layers)); if (!layers) { diff --git a/heart/src/server.c b/heart/src/server.c index 77efba2..f406f79 100644 --- a/heart/src/server.c +++ b/heart/src/server.c @@ -2,9 +2,12 @@ #include "xdg_impl.h" #include "seat_impl.h" #include "output_impl.h" +#include "layer_shell_impl.h" #include #include #include +#include +#include #include #include #include @@ -19,11 +22,23 @@ #include #include -static void handle_backend_destroyed(struct wl_listener *listener, void *data) { +static void handle_headless_backend_destroyed(struct wl_listener *listener, + void *data) { struct hrt_server *server = - wl_container_of(listener, server, backend_destroy); - wl_display_terminate(server->wl_display); + wl_container_of(listener, server, destroy_listener.headless); + wlr_log(WLR_DEBUG, "Headless Backend destroyed"); + wl_list_remove(&listener->link); + + // The fallback output needs to be freed at some point, but I'm + // not actually sure where it's safe. This seems like the obvious choice: + free(server->fallback_output); +} +static void handle_auto_backend_destroyed(struct wl_listener *listener, + void *data) { + struct hrt_server *server = + wl_container_of(listener, server, destroy_listener.backend); + wlr_log(WLR_DEBUG, "Backend destroyed"); wl_list_remove(&listener->link); } @@ -31,19 +46,35 @@ bool hrt_server_init(struct hrt_server *server, const struct hrt_output_callbacks *output_callbacks, const struct hrt_seat_callbacks *seat_callbacks, const struct hrt_view_callbacks *view_callbacks, + const struct hrt_layer_shell_callbacks *layer_shell_callbacks, enum wlr_log_importance log_level) { wlr_log_init(log_level, NULL); server->wl_display = wl_display_create(); - server->backend = wlr_backend_autocreate( - wl_display_get_event_loop(server->wl_display), &server->session); + struct wl_event_loop *event_loop = + wl_display_get_event_loop(server->wl_display); + server->backend = wlr_backend_autocreate(event_loop, &server->session); - server->backend_destroy.notify = &handle_backend_destroyed; - wl_signal_add(&server->backend->events.destroy, &server->backend_destroy); + server->destroy_listener.backend.notify = &handle_auto_backend_destroyed; + wl_signal_add(&server->backend->events.destroy, &server->destroy_listener.backend); if (!server->backend) { return false; } + server->headless_backend = wlr_headless_backend_create(event_loop); + server->destroy_listener.headless.notify = &handle_headless_backend_destroyed; + wl_signal_add(&server->headless_backend->events.destroy, + &server->destroy_listener.headless); + + if (!server->headless_backend) { + return false; + } else { + wlr_multi_backend_add(server->backend, server->headless_backend); + } + struct wlr_output *fallback = + wlr_headless_add_output(server->headless_backend, 800, 800); + server->fallback_output = hrt_output_create(server, fallback); + server->renderer = wlr_renderer_autocreate(server->backend); if (!server->renderer) { return false; @@ -67,7 +98,7 @@ bool hrt_server_init(struct hrt_server *server, wlr_data_control_manager_v1_create(server->wl_display); wlr_gamma_control_manager_v1_create(server->wl_display); - server->scene = wlr_scene_create(); + server->scene = wlr_scene_create(); server->output_layout = wlr_output_layout_create(server->wl_display); server->view_callbacks = view_callbacks; @@ -83,6 +114,15 @@ bool hrt_server_init(struct hrt_server *server, return false; } + // Check if this arg was provided so we don't need to specify this for + // test compositors. + if (layer_shell_callbacks) { + if (!hrt_layer_shell_init(server)) { + return false; + } + server->layer_shell_callbacks = layer_shell_callbacks; + } + return true; } @@ -146,5 +186,5 @@ struct hrt_seat *hrt_server_seat(struct hrt_server *server) { } size_t hrt_server_struct_size() { - return sizeof(struct hrt_server); + return sizeof(struct hrt_server); } diff --git a/lisp/bindings/hrt-bindings.lisp b/lisp/bindings/hrt-bindings.lisp index 1f0e32d..2dec543 100644 --- a/lisp/bindings/hrt-bindings.lisp +++ b/lisp/bindings/hrt-bindings.lisp @@ -232,63 +232,6 @@ set the width and height of views." (cffi:defcfun ("hrt_output_serial" hrt-output-serial) :string (output (:pointer (:struct hrt-output)))) -;; next section imported from file build/include/hrt/hrt_server.h - -(cffi:defcstruct hrt-server - (wl-display :pointer #| (:struct wl-display) |# ) - (backend :pointer #| (:struct wlr-backend) |# ) - (backend-destroy (:struct wl-listener)) - (session :pointer #| (:struct wlr-session) |# ) - (renderer :pointer #| (:struct wlr-renderer) |# ) - (compositor :pointer #| (:struct wlr-compositor) |# ) - (allocator :pointer #| (:struct wlr-allocator) |# ) - (scene :pointer #| (:struct wlr-scene) |# ) - (scene-layout :pointer #| (:struct wlr-scene-output-layout) |# ) - (new-output (:struct wl-listener)) - (output-manager :pointer #| (:struct wlr-output-manager-v1) |# ) - (output-layout :pointer #| (:struct wlr-output-layout) |# ) - (output-layout-changed (:struct wl-listener)) - (output-manager-apply (:struct wl-listener)) - (output-manager-test (:struct wl-listener)) - (output-manager-destroy (:struct wl-listener)) - (seat (:struct hrt-seat)) - (xdg-shell :pointer #| (:struct wlr-xdg-shell) |# ) - (new-xdg-toplevel (:struct wl-listener)) - (new-xdg-popup (:struct wl-listener)) - (output-callback (:pointer (:struct hrt-output-callbacks))) - (view-callbacks (:pointer (:struct hrt-view-callbacks)))) - -(declaim (inline hrt-server-init)) -(cffi:defcfun ("hrt_server_init" hrt-server-init) :bool - (server (:pointer (:struct hrt-server))) - (output-callbacks (:pointer (:struct hrt-output-callbacks))) - (seat-callbacks (:pointer (:struct hrt-seat-callbacks))) - (view-callbacks (:pointer (:struct hrt-view-callbacks))) - (log-level :int #| enum wlr-log-importance |#)) - -(declaim (inline hrt-server-start)) -(cffi:defcfun ("hrt_server_start" hrt-server-start) :bool - (server (:pointer (:struct hrt-server)))) - -(declaim (inline hrt-server-stop)) -(cffi:defcfun ("hrt_server_stop" hrt-server-stop) :void - (server (:pointer (:struct hrt-server)))) - -(declaim (inline hrt-server-finish)) -(cffi:defcfun ("hrt_server_finish" hrt-server-finish) :void - (server (:pointer (:struct hrt-server)))) - -(declaim (inline hrt-server-scene-tree)) -(cffi:defcfun ("hrt_server_scene_tree" hrt-server-scene-tree) :pointer #| (:struct wlr-scene-tree) |# - (server (:pointer (:struct hrt-server)))) - -(declaim (inline hrt-server-seat)) -(cffi:defcfun ("hrt_server_seat" hrt-server-seat) (:pointer (:struct hrt-seat)) - (server (:pointer (:struct hrt-server)))) - -(declaim (inline hrt-server-struct-size)) -(cffi:defcfun ("hrt_server_struct_size" hrt-server-struct-size) :size) - ;; next section imported from file build/include/hrt/hrt_scene.h (cffi:defcstruct hrt-scene-root-listeners @@ -307,7 +250,8 @@ set the width and height of views." (background :pointer #| (:struct wlr-scene-tree) |# ) (bottom :pointer #| (:struct wlr-scene-tree) |# ) (top :pointer #| (:struct wlr-scene-tree) |# ) - (overlay :pointer #| (:struct wlr-scene-tree) |# )) + (overlay :pointer #| (:struct wlr-scene-tree) |# ) + (output (:pointer (:struct hrt-output)))) (cffi:defcstruct hrt-scene-group (normal :pointer #| (:struct wlr-scene-tree) |# ) @@ -334,6 +278,11 @@ set the width and height of views." (cffi:defcfun ("hrt_scene_output_destroy" hrt-scene-output-destroy) :void (output (:pointer (:struct hrt-scene-output)))) +(declaim (inline hrt-scene-output-get-layer)) +(cffi:defcfun ("hrt_scene_output_get_layer" hrt-scene-output-get-layer) :pointer #| (:struct wlr-scene-tree) |# + (output (:pointer (:struct hrt-scene-output))) + (layer-type :int #| enum zwlr-layer-shell-v1-layer |#)) + (declaim (inline hrt-scene-group-create)) (cffi:defcfun ("hrt_scene_group_create" hrt-scene-group-create) (:pointer (:struct hrt-scene-group)) (parent (:pointer (:struct hrt-scene-root)))) @@ -363,7 +312,7 @@ set the width and height of views." (destination (:pointer (:struct hrt-scene-group)))) (declaim (inline hrt-scene-group-normal)) -(cffi:defcfun ("hrt_scene_group_normal" hrt-scene-group-normal) :pointer #| (:struct wlr-scene-tree) |# +(cffi:defcfun ("hrt_scene_group_normal" hrt-scene-group-normal) :pointer #| (:struct wlr-scene-tree) |# (group (:pointer (:struct hrt-scene-group)))) (declaim (inline hrt-scene-create-fullscreen-node)) @@ -397,3 +346,120 @@ Returns the view that was in the node." (cffi:defcfun ("hrt_scene_fullscreen_configure" hrt-scene-fullscreen-configure) :uint32 (group (:pointer (:struct hrt-scene-fullscreen-node))) (output (:pointer (:struct hrt-output)))) + +;; next section imported from file build/include/hrt/hrt_layer_shell.h + +(cffi:defcstruct hrt-layer-shell-surface-events + (commit (:struct wl-listener)) + (map (:struct wl-listener)) + (unmap (:struct wl-listener)) + (new-popup (:struct wl-listener)) + (scene-destroy (:struct wl-listener))) + +(cffi:defcstruct hrt-layer-shell-surface + (layer-surface :pointer #| (:struct wlr-layer-surface-v1) |# ) + (scene-layer :pointer #| (:struct wlr-scene-layer-surface-v1) |# ) + (events (:struct hrt-layer-shell-surface-events))) + +(cffi:defctype layer-shell-event-handler :pointer #| function ptr void (struct hrt_layer_shell_surface *) |#) + +(cffi:defcstruct hrt-layer-shell-callbacks + (new-layer-surface layer-shell-event-handler)) + +(declaim (inline hrt-layer-shell-surface-create)) +(cffi:defcfun ("hrt_layer_shell_surface_create" hrt-layer-shell-surface-create) (:pointer (:struct hrt-layer-shell-surface)) + (surface :pointer #| (:struct wlr-layer-surface-v1) |# )) + +(declaim (inline hrt-layer-shell-surface-abort)) +(cffi:defcfun ("hrt_layer_shell_surface_abort" hrt-layer-shell-surface-abort) :void + "Destroy a partially created hrt_layer_shell-surface object" + (surface (:pointer (:struct hrt-layer-shell-surface)))) + +(declaim (inline hrt-layer-surface-output)) +(cffi:defcfun ("hrt_layer_surface_output" hrt-layer-surface-output) (:pointer (:struct hrt-output)) + (layer-shell (:pointer (:struct hrt-layer-shell-surface)))) + +(declaim (inline hrt-layer-shell-surface-set-output)) +(cffi:defcfun ("hrt_layer_shell_surface_set_output" hrt-layer-shell-surface-set-output) :void + (layer-shell (:pointer (:struct hrt-layer-shell-surface))) + (output (:pointer (:struct hrt-output)))) + +(declaim (inline hrt-layer-shell-surface-place)) +(cffi:defcfun ("hrt_layer_shell_surface_place" hrt-layer-shell-surface-place) :void + "Place a freshly-initialized surface in an output. Should only be called once during +intial placement." + (surface (:pointer (:struct hrt-layer-shell-surface))) + (output (:pointer (:struct hrt-scene-output)))) + +(declaim (inline hrt-layer-shell-finish-init)) +(cffi:defcfun ("hrt_layer_shell_finish_init" hrt-layer-shell-finish-init) :void + "Finish initializing the layer shell object" + (surface (:pointer (:struct hrt-layer-shell-surface)))) + +;; next section imported from file build/include/hrt/hrt_server.h + +(cffi:defcstruct hrt-server-destroy-listener + (backend (:struct wl-listener)) + (headless (:struct wl-listener)) + (output-manager (:struct wl-listener)) + (layer-shell (:struct wl-listener))) + +(cffi:defcstruct hrt-server + (wl-display :pointer #| (:struct wl-display) |# ) + (backend :pointer #| (:struct wlr-backend) |# ) + (headless-backend :pointer #| (:struct wlr-backend) |# ) + (session :pointer #| (:struct wlr-session) |# ) + (renderer :pointer #| (:struct wlr-renderer) |# ) + (compositor :pointer #| (:struct wlr-compositor) |# ) + (allocator :pointer #| (:struct wlr-allocator) |# ) + (scene :pointer #| (:struct wlr-scene) |# ) + (scene-layout :pointer #| (:struct wlr-scene-output-layout) |# ) + (new-output (:struct wl-listener)) + (output-manager :pointer #| (:struct wlr-output-manager-v1) |# ) + (output-layout :pointer #| (:struct wlr-output-layout) |# ) + (output-layout-changed (:struct wl-listener)) + (output-manager-apply (:struct wl-listener)) + (output-manager-test (:struct wl-listener)) + (seat (:struct hrt-seat)) + (fallback-output (:pointer (:struct hrt-output))) + (xdg-shell :pointer #| (:struct wlr-xdg-shell) |# ) + (new-xdg-toplevel (:struct wl-listener)) + (new-xdg-popup (:struct wl-listener)) + (layer-shell :pointer #| (:struct wlr-layer-shell-v1) |# ) + (new-layer-shell (:struct wl-listener)) + (destroy-listener (:struct hrt-server-destroy-listener)) + (output-callback (:pointer (:struct hrt-output-callbacks))) + (view-callbacks (:pointer (:struct hrt-view-callbacks))) + (layer-shell-callbacks (:pointer (:struct hrt-layer-shell-callbacks)))) + +(declaim (inline hrt-server-init)) +(cffi:defcfun ("hrt_server_init" hrt-server-init) :bool + (server (:pointer (:struct hrt-server))) + (output-callbacks (:pointer (:struct hrt-output-callbacks))) + (seat-callbacks (:pointer (:struct hrt-seat-callbacks))) + (view-callbacks (:pointer (:struct hrt-view-callbacks))) + (layer-shell-callbacks (:pointer (:struct hrt-layer-shell-callbacks))) + (log-level :int #| enum wlr-log-importance |#)) + +(declaim (inline hrt-server-start)) +(cffi:defcfun ("hrt_server_start" hrt-server-start) :bool + (server (:pointer (:struct hrt-server)))) + +(declaim (inline hrt-server-stop)) +(cffi:defcfun ("hrt_server_stop" hrt-server-stop) :void + (server (:pointer (:struct hrt-server)))) + +(declaim (inline hrt-server-finish)) +(cffi:defcfun ("hrt_server_finish" hrt-server-finish) :void + (server (:pointer (:struct hrt-server)))) + +(declaim (inline hrt-server-scene-tree)) +(cffi:defcfun ("hrt_server_scene_tree" hrt-server-scene-tree) :pointer #| (:struct wlr-scene-tree) |# + (server (:pointer (:struct hrt-server)))) + +(declaim (inline hrt-server-seat)) +(cffi:defcfun ("hrt_server_seat" hrt-server-seat) (:pointer (:struct hrt-seat)) + (server (:pointer (:struct hrt-server)))) + +(declaim (inline hrt-server-struct-size)) +(cffi:defcfun ("hrt_server_struct_size" hrt-server-struct-size) :size) diff --git a/lisp/bindings/hrt-bindings.yml b/lisp/bindings/hrt-bindings.yml index c434482..a32afee 100644 --- a/lisp/bindings/hrt-bindings.yml +++ b/lisp/bindings/hrt-bindings.yml @@ -10,8 +10,9 @@ files: - build/include/hrt/hrt_input.h - build/include/hrt/hrt_view.h - build/include/hrt/hrt_output.h - - build/include/hrt/hrt_server.h - build/include/hrt/hrt_scene.h + - build/include/hrt/hrt_layer_shell.h + - build/include/hrt/hrt_server.h pointer-expansion: include: match: "hrt.*" diff --git a/lisp/bindings/layer-shell.lisp b/lisp/bindings/layer-shell.lisp new file mode 100644 index 0000000..7580b7b --- /dev/null +++ b/lisp/bindings/layer-shell.lisp @@ -0,0 +1,9 @@ +(in-package #:hrt) + +;; (declaim (inline layer-surface-output)) +(defun layer-surface-output (layer-surface) + (declare (type cffi:foreign-pointer layer-surface)) + (let ((output (hrt-layer-surface-output layer-surface))) + (if (cffi:null-pointer-p output) + nil + output))) diff --git a/lisp/bindings/package.lisp b/lisp/bindings/package.lisp index c951d5d..c61a80a 100644 --- a/lisp/bindings/package.lisp +++ b/lisp/bindings/package.lisp @@ -15,6 +15,8 @@ (:export #:hrt-output-callbacks #:hrt-seat-callbacks #:hrt-view-callbacks + #:hrt-layer-shell-callbacks + #:new-layer-surface #:new-view #:hrt-view #:view-mapped @@ -86,4 +88,10 @@ #:hrt-scene-fullscreen-node-destroy #:hrt-scene-fullscreen-configure #:scene-init-view - #:load-foreign-libraries)) + #:load-foreign-libraries + ;; layer shell methods + #:layer-surface-output + #:hrt-layer-shell-surface-set-output + #:hrt-layer-shell-surface-abort + #:hrt-layer-shell-surface-place + #:hrt-layer-shell-finish-init)) diff --git a/lisp/events.lisp b/lisp/events.lisp index 54c2df0..73aadd3 100644 --- a/lisp/events.lisp +++ b/lisp/events.lisp @@ -45,3 +45,7 @@ (cffi:defcallback handle-output-layout-change :void () (mahogany-state-output-reconfigure *compositor-state*)) + +(cffi:defcallback handle-layer-shell-recieved :void ((surface :pointer)) + (log-string :trace "Layer shell recieved") + (mahogany-state-layer-shell-handle *compositor-state* surface)) diff --git a/lisp/group.lisp b/lisp/group.lisp index 3681871..176dff3 100644 --- a/lisp/group.lisp +++ b/lisp/group.lisp @@ -95,8 +95,8 @@ (hrt:output-position (mahogany-output-hrt-output output)) (multiple-value-bind (width height) (hrt:output-resolution (mahogany-output-hrt-output output)) - (let ((new-tree (tree:tree-container-add tree-container - :x x :y y :width width :height height))) + (let ((new-tree (tree:tree-container-add tree-container output + :x x :y y :width width :height height))) (setf (gethash (mahogany-output-full-name output) output-map) new-tree) (when (not current-frame) (let ((first-leaf (tree:find-first-leaf new-tree))) @@ -124,6 +124,10 @@ to match." (declare (ignore found key)) value))) +(defun group-current-output (group) + (let ((output-node (tree:frame-parent (tree:find-root-frame (mahogany-group-current-frame group))))) + (tree:output-node-output output-node))) + (defun group-remove-output (group output seat) (declare (type mahogany-output output) (type mahogany-group group)) @@ -133,14 +137,14 @@ to match." (let* ((output-name (mahogany-output-full-name output)) (tree (gethash output-name output-map))) (remhash output-name output-map) - (when (equalp tree (tree:find-root-frame (mahogany-group-current-frame group))) + (when (equalp output (group-current-output group)) (group-unfocus-frame group (mahogany-group-current-frame group) seat) (alexandria:when-let ((other-tree (%first-hash-table-value output-map))) (group-focus-frame group (tree:find-first-leaf other-tree) seat))) (when (and (mahogany-group-current-frame group) (= 0 (hash-table-count output-map))) (group-unfocus-frame group (mahogany-group-current-frame group) seat)) (tree:remove-frame tree (lambda (x) (alexandria:when-let ((v (tree:frame-view x))) - (%add-hidden hidden-views v))))))) + (%add-hidden hidden-views v))))))) (defun %group-add-view (group view) (declare (type mahogany-group group) diff --git a/lisp/input.lisp b/lisp/input.lisp index f9d2dee..c5356e3 100644 --- a/lisp/input.lisp +++ b/lisp/input.lisp @@ -53,7 +53,9 @@ (found (tree:frame-at (mahogany-group-tree-container group) (hrt:hrt-seat-cursor-lx seat) (hrt:hrt-seat-cursor-ly seat)))) - (group-focus-frame group found seat))) + (if found + (group-focus-frame group found seat) + nil))) (cffi:defcallback handle-mouse-wheel-event :void ((seat (:pointer (:struct hrt:hrt-seat))) diff --git a/lisp/main.lisp b/lisp/main.lisp index 5158e99..7489c82 100644 --- a/lisp/main.lisp +++ b/lisp/main.lisp @@ -43,15 +43,20 @@ further up. " (hrt:view-destroyed handle-view-destroyed-event) (hrt:request-fullscreen handle-request-fullscreen))) +(defun init-layer-shell-callbacks (layer-shell-callbacks) + (init-callback-struct layer-shell-callbacks (:struct hrt:hrt-layer-shell-callbacks) + (hrt:new-layer-surface handle-layer-shell-recieved))) + (defun run-server (args) (disable-fpu-exceptions) (hrt:load-foreign-libraries) (log-init :level (intern (gethash 'loglevel args) 'keyword)) (enable-debugger) (cffi:with-foreign-objects ((output-callbacks '(:struct hrt:hrt-output-callbacks)) - (seat-callbacks '(:struct hrt:hrt-seat-callbacks)) - (view-callbacks '(:struct hrt:hrt-view-callbacks)) - (server '(:struct hrt:hrt-server))) + (seat-callbacks '(:struct hrt:hrt-seat-callbacks)) + (view-callbacks '(:struct hrt:hrt-view-callbacks)) + (layer-shell-callbacks '(:struct hrt:hrt-layer-shell-callbacks)) + (server '(:struct hrt:hrt-server))) (init-callback-struct output-callbacks (:struct hrt:hrt-output-callbacks) (hrt:output-added handle-new-output) (hrt:output-removed handle-output-removed) @@ -61,9 +66,13 @@ further up. " (hrt:wheel-event handle-mouse-wheel-event) (hrt:keyboard-keypress-event keyboard-callback)) (init-view-callbacks view-callbacks) + (init-layer-shell-callbacks layer-shell-callbacks) (server-state-init *compositor-state* server - output-callbacks seat-callbacks view-callbacks + output-callbacks + seat-callbacks + view-callbacks + layer-shell-callbacks :debug-level 3) (log-string :debug "Initialized mahogany state") (if (gethash 'no-init-file args) diff --git a/lisp/state.lisp b/lisp/state.lisp index a74672d..9aea251 100644 --- a/lisp/state.lisp +++ b/lisp/state.lisp @@ -22,10 +22,12 @@ default-group))) (defun server-state-init (state server output-callbacks seat-callbacks view-callbacks + layer-shell-callbacks &key (debug-level 3)) (setf (mahogany-state-server state) server) (hrt:hrt-server-init server output-callbacks seat-callbacks view-callbacks + layer-shell-callbacks debug-level) (setf (mahogany-state-scene state) (hrt:hrt-scene-root-create (hrt:hrt-server-scene-tree server))) (let ((default-group (%add-group state *default-group-name* 1))) @@ -257,3 +259,26 @@ (defun mahogany-set-keymap (state &key (rules (cffi:null-pointer)) (keymap-flags :no-flags)) (let ((seat (hrt:hrt-server-seat (mahogany-state-server state)))) (hrt:hrt-seat-set-keymap seat rules keymap-flags))) + +(defun %get-or-autoassign-output (state hrt-layer-shell) + (declare (type mahogany-state state)) + (alexandria:if-let ((hrt-output (hrt:layer-surface-output hrt-layer-shell))) + (with-accessors ((outputs mahogany-state-outputs)) state + (the (or mahogany-output null) (%find-output hrt-output outputs))) + (let ((current-output (group-current-output (mahogany-current-group state)))) + ;; TODO: try to use the fallback output: + (unless current-output + (log-string :error "Could not auto-assign output to layer surface") + (return-from %get-or-autoassign-output nil)) + (hrt:hrt-layer-shell-surface-set-output hrt-layer-shell + (mahogany-output-hrt-output current-output)) + (the mahogany-output current-output)))) + +(defun mahogany-state-layer-shell-handle (state hrt-layer-shell) + (declare (type mahogany-state state)) + (alexandria:if-let ((output (%get-or-autoassign-output state hrt-layer-shell))) + (progn + (hrt:hrt-layer-shell-surface-place hrt-layer-shell (mahogany-output-hrt-scene output)) + (hrt:hrt-layer-shell-finish-init hrt-layer-shell)) + (progn + (hrt:hrt-layer-shell-surface-abort hrt-layer-shell)))) diff --git a/lisp/tree/package.lisp b/lisp/tree/package.lisp index 08d1744..7350c82 100644 --- a/lisp/tree/package.lisp +++ b/lisp/tree/package.lisp @@ -33,6 +33,7 @@ #:get-populated-frames #:root-frame-p #:find-root-frame + #:find-output #:find-first-leaf #:mark-frame-focused #:unmark-frame-focused @@ -43,4 +44,5 @@ #:frame-view #:frame-next #:frame-prev - #:leafs-in)) + #:leafs-in + #:output-node-output)) diff --git a/lisp/tree/tree-interface.lisp b/lisp/tree/tree-interface.lisp index e61ccf8..d903d74 100644 --- a/lisp/tree/tree-interface.lisp +++ b/lisp/tree/tree-interface.lisp @@ -69,7 +69,9 @@ of an already existing frame with the `set-split-frame-type` function") ((parent :initarg :parent :initform nil :type (or null tree-container) - :accessor frame-parent))) + :accessor frame-parent) + (output :initarg :output + :reader output-node-output))) (deftype split-frame-type () '(member :vertical :horizontal)) @@ -165,10 +167,10 @@ a view assigned to it.")) (do ((cur-frame frame (frame-parent cur-frame))) ((root-frame-p cur-frame) cur-frame))) -(defun tree-container-add (tree-container &key (x 0) (y 0) (width 100) (height 100)) +(defun tree-container-add (tree-container output &key (x 0) (y 0) (width 100) (height 100)) (declare (type tree-container tree-container)) (with-accessors ((container-children tree-children)) tree-container - (let* ((new-output (make-instance 'output-node :parent tree-container)) + (let* ((new-output (make-instance 'output-node :parent tree-container :output output)) (new-tree (make-instance 'view-frame :x x :y y :width width :height height :parent new-output)) (prev-output (first container-children))) diff --git a/mahogany.asd b/mahogany.asd index 2d45a4e..e579dd4 100644 --- a/mahogany.asd +++ b/mahogany.asd @@ -35,6 +35,7 @@ (:file "hrt-debug" :if-feature :hrt-debug) (:file "wrappers") (:file "view") + (:file "layer-shell") (:file "scene-group"))) (:module keyboard :depends-on ("util")