diff --git a/examples/fullscreen-shell.c b/examples/fullscreen-shell.c new file mode 100644 index 00000000..7f2c9c2b --- /dev/null +++ b/examples/fullscreen-shell.c @@ -0,0 +1,248 @@ +#define _POSIX_C_SOURCE 200112L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * A minimal fullscreen-shell server. It only supports rendering. + */ + +struct fullscreen_server { + struct wl_display *wl_display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + + struct wlr_fullscreen_shell_v1 *fullscreen_shell; + struct wl_listener present_surface; + + struct wlr_output_layout *output_layout; + struct wl_list outputs; + struct wl_listener new_output; +}; + +struct fullscreen_output { + struct wl_list link; + struct fullscreen_server *server; + struct wlr_output *wlr_output; + struct wlr_surface *surface; + struct wl_listener surface_destroy; + + struct wl_listener frame; +}; + +struct render_data { + struct wlr_output *output; + struct wlr_renderer *renderer; + struct tinywl_view *view; + struct timespec *when; +}; + +static void render_surface(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct render_data *rdata = data; + struct wlr_output *output = rdata->output; + + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (texture == NULL) { + return; + } + + struct wlr_box box = { + .x = sx * output->scale, + .y = sy * output->scale, + .width = surface->current.width * output->scale, + .height = surface->current.height * output->scale, + }; + + float matrix[9]; + enum wl_output_transform transform = + wlr_output_transform_invert(surface->current.transform); + wlr_matrix_project_box(matrix, &box, transform, 0, + output->transform_matrix); + + wlr_render_texture_with_matrix(rdata->renderer, texture, matrix, 1); + + wlr_surface_send_frame_done(surface, rdata->when); +} + +static void output_handle_frame(struct wl_listener *listener, void *data) { + struct fullscreen_output *output = + wl_container_of(listener, output, frame); + struct wlr_renderer *renderer = output->server->renderer; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + int width, height; + wlr_output_effective_resolution(output->wlr_output, &width, &height); + + if (!wlr_output_make_current(output->wlr_output, NULL)) { + return; + } + + wlr_renderer_begin(renderer, width, height); + + float color[4] = {0.3, 0.3, 0.3, 1.0}; + wlr_renderer_clear(renderer, color); + + if (output->surface != NULL) { + struct render_data rdata = { + .output = output->wlr_output, + .renderer = renderer, + .when = &now, + }; + wlr_surface_for_each_surface(output->surface, render_surface, &rdata); + } + + wlr_renderer_end(renderer); + wlr_output_swap_buffers(output->wlr_output, NULL, NULL); +} + +static void output_set_surface(struct fullscreen_output *output, + struct wlr_surface *surface); + +static void output_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct fullscreen_output *output = + wl_container_of(listener, output, surface_destroy); + output_set_surface(output, NULL); +} + +static void output_set_surface(struct fullscreen_output *output, + struct wlr_surface *surface) { + if (output->surface == surface) { + return; + } + + if (output->surface != NULL) { + wl_list_remove(&output->surface_destroy.link); + output->surface = NULL; + } + + if (surface != NULL) { + output->surface_destroy.notify = output_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &output->surface_destroy); + output->surface = surface; + } + + wlr_log(WLR_DEBUG, "Presenting surface %p on output %s", + surface, output->wlr_output->name); +} + +static void server_handle_new_output(struct wl_listener *listener, void *data) { + struct fullscreen_server *server = + wl_container_of(listener, server, new_output); + struct wlr_output *wlr_output = data; + + if (!wl_list_empty(&wlr_output->modes)) { + struct wlr_output_mode *mode = + wl_container_of(wlr_output->modes.prev, mode, link); + wlr_output_set_mode(wlr_output, mode); + } + + struct fullscreen_output *output = + calloc(1, sizeof(struct fullscreen_output)); + output->wlr_output = wlr_output; + output->server = server; + output->frame.notify = output_handle_frame; + wl_signal_add(&wlr_output->events.frame, &output->frame); + wl_list_insert(&server->outputs, &output->link); + + wlr_output_layout_add_auto(server->output_layout, wlr_output); + wlr_output_create_global(wlr_output); +} + +static void server_handle_present_surface(struct wl_listener *listener, + void *data) { + struct fullscreen_server *server = + wl_container_of(listener, server, present_surface); + struct wlr_fullscreen_shell_v1_present_surface_event *event = data; + + struct fullscreen_output *output; + wl_list_for_each(output, &server->outputs, link) { + if (event->output == NULL || event->output == output->wlr_output) { + output_set_surface(output, event->surface); + } + } +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + + char *startup_cmd = NULL; + + int c; + while ((c = getopt(argc, argv, "s:")) != -1) { + switch (c) { + case 's': + startup_cmd = optarg; + break; + default: + printf("usage: %s [-s startup-command]\n", argv[0]); + return EXIT_FAILURE; + } + } + if (optind < argc) { + printf("usage: %s [-s startup-command]\n", argv[0]); + return EXIT_FAILURE; + } + + struct fullscreen_server server = {0}; + server.wl_display = wl_display_create(); + server.backend = wlr_backend_autocreate(server.wl_display, NULL); + server.renderer = wlr_backend_get_renderer(server.backend); + wlr_renderer_init_wl_display(server.renderer, server.wl_display); + + wlr_compositor_create(server.wl_display, server.renderer); + wlr_linux_dmabuf_v1_create(server.wl_display, server.renderer); + + server.output_layout = wlr_output_layout_create(); + + wl_list_init(&server.outputs); + server.new_output.notify = server_handle_new_output; + wl_signal_add(&server.backend->events.new_output, &server.new_output); + + server.fullscreen_shell = wlr_fullscreen_shell_v1_create(server.wl_display); + server.present_surface.notify = server_handle_present_surface; + wl_signal_add(&server.fullscreen_shell->events.present_surface, + &server.present_surface); + + const char *socket = wl_display_add_socket_auto(server.wl_display); + if (!socket) { + wl_display_destroy(server.wl_display); + return EXIT_FAILURE; + } + + if (!wlr_backend_start(server.backend)) { + wl_display_destroy(server.wl_display); + return EXIT_FAILURE; + } + + setenv("WAYLAND_DISPLAY", socket, true); + if (startup_cmd != NULL) { + if (fork() == 0) { + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL); + } + } + + wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", + socket); + wl_display_run(server.wl_display); + + wl_display_destroy_clients(server.wl_display); + wl_display_destroy(server.wl_display); + return 0; +} diff --git a/examples/meson.build b/examples/meson.build index 0811d1a2..8d38b888 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -112,11 +112,15 @@ examples = { 'text-input': { 'src': 'text-input.c', 'dep': [wayland_cursor, wayland_client, wlr_protos, wlroots], - }, + }, 'foreign-toplevel': { 'src': 'foreign-toplevel.c', 'dep': [wayland_client, wlr_protos, wlroots], }, + 'fullscreen-shell': { + 'src': 'fullscreen-shell.c', + 'dep': [wlr_protos, wlroots], + }, } foreach name, info : examples diff --git a/include/wlr/types/meson.build b/include/wlr/types/meson.build index dfb55ec9..f9075bea 100644 --- a/include/wlr/types/meson.build +++ b/include/wlr/types/meson.build @@ -6,6 +6,7 @@ install_headers( 'wlr_data_device.h', 'wlr_export_dmabuf_v1.h', 'wlr_foreign_toplevel_management_v1.h', + 'wlr_fullscreen_shell_v1.h', 'wlr_gamma_control_v1.h', 'wlr_gamma_control.h', 'wlr_gtk_primary_selection.h', @@ -22,8 +23,8 @@ install_headers( 'wlr_output_damage.h', 'wlr_output_layout.h', 'wlr_output.h', - 'wlr_pointer.h', 'wlr_pointer_constraints_v1.h', + 'wlr_pointer.h', 'wlr_presentation_time.h', 'wlr_primary_selection.h', 'wlr_region.h', diff --git a/include/wlr/types/wlr_fullscreen_shell_v1.h b/include/wlr/types/wlr_fullscreen_shell_v1.h new file mode 100644 index 00000000..a904d966 --- /dev/null +++ b/include/wlr/types/wlr_fullscreen_shell_v1.h @@ -0,0 +1,41 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_FULLSCREEN_SHELL_V1_H +#define WLR_TYPES_WLR_FULLSCREEN_SHELL_V1_H + +#include +#include "fullscreen-shell-unstable-v1-protocol.h" + +struct wlr_fullscreen_shell_v1 { + struct wl_global *global; + struct wl_list resources; + + struct { + struct wl_signal destroy; + // wlr_fullscreen_shell_v1_present_surface_event + struct wl_signal present_surface; + } events; + + struct wl_listener display_destroy; + + void *data; +}; + +struct wlr_fullscreen_shell_v1_present_surface_event { + struct wl_client *client; + struct wlr_surface *surface; // can be NULL + enum zwp_fullscreen_shell_v1_present_method method; + struct wlr_output *output; // can be NULL +}; + +struct wlr_fullscreen_shell_v1 *wlr_fullscreen_shell_v1_create( + struct wl_display *display); +void wlr_fullscreen_shell_v1_destroy(struct wlr_fullscreen_shell_v1 *shell); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 6d5acf37..93820d57 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -13,14 +13,15 @@ endif protocols = [ [wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'], [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], + [wl_protocol_dir, 'unstable/fullscreen-shell/fullscreen-shell-unstable-v1.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'], [wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'], [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], - [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], - [wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'], 'gamma-control.xml', 'gtk-primary-selection.xml', 'idle.xml', @@ -30,8 +31,8 @@ protocols = [ 'text-input-unstable-v3.xml', 'virtual-keyboard-unstable-v1.xml', 'wlr-export-dmabuf-unstable-v1.xml', - 'wlr-gamma-control-unstable-v1.xml', 'wlr-foreign-toplevel-management-unstable-v1.xml', + 'wlr-gamma-control-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml', @@ -40,17 +41,17 @@ protocols = [ client_protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], - [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'], - [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], [wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], 'idle.xml', 'input-method-unstable-v2.xml', 'screenshooter.xml', 'text-input-unstable-v3.xml', 'wlr-export-dmabuf-unstable-v1.xml', - 'wlr-gamma-control-unstable-v1.xml', 'wlr-foreign-toplevel-management-unstable-v1.xml', + 'wlr-gamma-control-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml', diff --git a/types/meson.build b/types/meson.build index 982f607d..a1dc213f 100644 --- a/types/meson.build +++ b/types/meson.build @@ -29,6 +29,7 @@ lib_wlr_types = static_library( 'wlr_cursor.c', 'wlr_export_dmabuf_v1.c', 'wlr_foreign_toplevel_management_v1.c', + 'wlr_fullscreen_shell_v1.c', 'wlr_gamma_control_v1.c', 'wlr_gamma_control.c', 'wlr_gtk_primary_selection.c', @@ -50,8 +51,8 @@ lib_wlr_types = static_library( 'wlr_presentation_time.c', 'wlr_primary_selection.c', 'wlr_region.c', - 'wlr_screencopy_v1.c', 'wlr_relative_pointer_v1.c', + 'wlr_screencopy_v1.c', 'wlr_screenshooter.c', 'wlr_server_decoration.c', 'wlr_surface.c', diff --git a/types/wlr_fullscreen_shell_v1.c b/types/wlr_fullscreen_shell_v1.c new file mode 100644 index 00000000..5bff72ff --- /dev/null +++ b/types/wlr_fullscreen_shell_v1.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include "util/signal.h" + +#define FULLSCREEN_SHELL_VERSION 1 + +static const struct zwp_fullscreen_shell_v1_interface shell_impl; + +static struct wlr_fullscreen_shell_v1 *shell_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_fullscreen_shell_v1_interface, &shell_impl)); + return wl_resource_get_user_data(resource); +} + +static void shell_handle_present_surface(struct wl_client *client, + struct wl_resource *shell_resource, + struct wl_resource *surface_resource, uint32_t method, + struct wl_resource *output_resource) { + struct wlr_fullscreen_shell_v1 *shell = shell_from_resource(shell_resource); + struct wlr_surface *surface = NULL; + if (surface_resource != NULL) { + surface = wlr_surface_from_resource(surface_resource); + } + struct wlr_output *output = NULL; + if (output_resource != NULL) { + output = wlr_output_from_resource(output_resource); + } + + struct wlr_fullscreen_shell_v1_present_surface_event event = { + .client = client, + .surface = surface, + .method = method, + .output = output, + }; + wlr_signal_emit_safe(&shell->events.present_surface, &event); +} + +static void shell_handle_present_surface_for_mode(struct wl_client *client, + struct wl_resource *shell_resource, + struct wl_resource *surface_resource, + struct wl_resource *output_resource, int32_t framerate, + uint32_t feedback_id) { + uint32_t version = wl_resource_get_version(shell_resource); + struct wl_resource *feedback_resource = + wl_resource_create(client, NULL, version, feedback_id); + if (feedback_resource == NULL) { + wl_resource_post_no_memory(shell_resource); + return; + } + + // TODO: add support for mode switch + zwp_fullscreen_shell_mode_feedback_v1_send_mode_failed(feedback_resource); + wl_resource_destroy(feedback_resource); +} + +static const struct zwp_fullscreen_shell_v1_interface shell_impl = { + .present_surface = shell_handle_present_surface, + .present_surface_for_mode = shell_handle_present_surface_for_mode, +}; + +static void shell_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void shell_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_fullscreen_shell_v1 *shell = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwp_fullscreen_shell_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &shell_impl, shell, + shell_handle_resource_destroy); + + wl_list_insert(&shell->resources, wl_resource_get_link(resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_fullscreen_shell_v1 *shell = + wl_container_of(listener, shell, display_destroy); + wlr_fullscreen_shell_v1_destroy(shell); +} + +struct wlr_fullscreen_shell_v1 *wlr_fullscreen_shell_v1_create( + struct wl_display *display) { + struct wlr_fullscreen_shell_v1 *shell = + calloc(1, sizeof(struct wlr_fullscreen_shell_v1)); + if (shell == NULL) { + return NULL; + } + wl_list_init(&shell->resources); + wl_signal_init(&shell->events.destroy); + wl_signal_init(&shell->events.present_surface); + + shell->global = wl_global_create(display, + &zwp_fullscreen_shell_v1_interface, FULLSCREEN_SHELL_VERSION, + shell, shell_bind); + if (shell->global == NULL) { + free(shell); + return NULL; + } + + shell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &shell->display_destroy); + + return shell; +} + +void wlr_fullscreen_shell_v1_destroy(struct wlr_fullscreen_shell_v1 *shell) { + if (shell == NULL) { + return; + } + wlr_signal_emit_safe(&shell->events.destroy, shell); + wl_list_remove(&shell->display_destroy.link); + wl_global_destroy(shell->global); + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &shell->resources) { + wl_resource_destroy(resource); + } + free(shell); +}