diff --git a/GNUmakefile b/GNUmakefile index 724b071..a1fc704 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -85,15 +85,33 @@ lib/renderers/wayland/wlr-layer-shell-unstable-v1.h: lib/renderers/wayland/wlr-l lib/renderers/wayland/wlr-layer-shell-unstable-v1.c: lib/renderers/wayland/wlr-layer-shell-unstable-v1.xml wayland-scanner private-code < $^ > $@ +lib/renderers/wayland/fractional-scale-v1.h: lib/renderers/wayland/fractional-scale-v1.xml + wayland-scanner client-header < $^ > $@ + +lib/renderers/wayland/fractional-scale-v1.c: lib/renderers/wayland/fractional-scale-v1.xml + wayland-scanner private-code < $^ > $@ + +lib/renderers/wayland/viewporter.h: lib/renderers/wayland/viewporter.xml + wayland-scanner client-header < $^ > $@ + +lib/renderers/wayland/viewporter.c: lib/renderers/wayland/viewporter.xml + wayland-scanner private-code < $^ > $@ + xdg-shell.a: private override LDFLAGS += -fPIC xdg-shell.a: private override CPPFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I wayland-client) xdg-shell.a: lib/renderers/wayland/xdg-shell.c wlr-layer-shell.a: private override LDFLAGS += -fPIC wlr-layer-shell.a: private override CPPFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I wayland-client) wlr-layer-shell.a: lib/renderers/wayland/wlr-layer-shell-unstable-v1.c lib/renderers/wayland/wlr-layer-shell-unstable-v1.h +fractional-scale.a: private override LDFLAGS += -fPIC +fractional-scale.a: private override CPPFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I wayland-client) +fractional-scale.a: lib/renderers/wayland/fractional-scale-v1.c lib/renderers/wayland/fractional-scale-v1.h +viewporter.a: private override LDFLAGS += -fPIC +viewporter.a: private override CPPFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I wayland-client) +viewporter.a: lib/renderers/wayland/viewporter.c lib/renderers/wayland/viewporter.h bemenu-renderer-wayland.so: private override LDLIBS += $(shell $(PKG_CONFIG) --libs wayland-client cairo pango pangocairo xkbcommon) bemenu-renderer-wayland.so: private override CPPFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I wayland-client cairo pango pangocairo xkbcommon) -bemenu-renderer-wayland.so: lib/renderers/cairo_renderer.h lib/renderers/wayland/wayland.c lib/renderers/wayland/wayland.h lib/renderers/wayland/registry.c lib/renderers/wayland/window.c xdg-shell.a wlr-layer-shell.a util.a +bemenu-renderer-wayland.so: lib/renderers/cairo_renderer.h lib/renderers/wayland/wayland.c lib/renderers/wayland/wayland.h lib/renderers/wayland/registry.c lib/renderers/wayland/window.c xdg-shell.a wlr-layer-shell.a fractional-scale.a viewporter.a util.a common.a: client/common/common.c client/common/common.h bemenu: common.a client/bemenu.c diff --git a/lib/renderers/cairo_renderer.h b/lib/renderers/cairo_renderer.h index 80e9cfe..8b3ef70 100644 --- a/lib/renderers/cairo_renderer.h +++ b/lib/renderers/cairo_renderer.h @@ -12,7 +12,7 @@ struct cairo { cairo_t *cr; cairo_surface_t *surface; PangoContext *pango; - int scale; + double scale; }; struct cairo_color { @@ -67,6 +67,8 @@ bm_cairo_create_for_surface(struct cairo *cairo, cairo_surface_t *surface) if (!(cairo->pango = pango_cairo_create_context(cairo->cr))) goto fail; + cairo_set_antialias(cairo->cr, CAIRO_ANTIALIAS_NONE); + cairo->surface = surface; assert(cairo->scale > 0); cairo_surface_set_device_scale(surface, cairo->scale, cairo->scale); diff --git a/lib/renderers/wayland/fractional-scale-v1.xml b/lib/renderers/wayland/fractional-scale-v1.xml new file mode 100644 index 0000000..350bfc0 --- /dev/null +++ b/lib/renderers/wayland/fractional-scale-v1.xml @@ -0,0 +1,102 @@ + + + + Copyright © 2022 Kenny Levinsen + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows a compositor to suggest for surfaces to render at + fractional scales. + + A client can submit scaled content by utilizing wp_viewport. This is done by + creating a wp_viewport object for the surface and setting the destination + rectangle to the surface size before the scale factor is applied. + + The buffer size is calculated by multiplying the surface size by the + intended scale. + + The wl_surface buffer scale should remain set to 1. + + If a surface has a surface-local size of 100 px by 50 px and wishes to + submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should + be used and the wp_viewport destination rectangle should be 100 px by 50 px. + + For toplevel surfaces, the size is rounded halfway away from zero. The + rounding algorithm for subsurface position and size is not defined. + + + + + A global interface for requesting surfaces to use fractional scales. + + + + + Informs the server that the client will not be using this protocol + object anymore. This does not affect any other objects, + wp_fractional_scale_v1 objects included. + + + + + + + + + + Create an add-on object for the the wl_surface to let the compositor + request fractional scales. If the given wl_surface already has a + wp_fractional_scale_v1 object associated, the fractional_scale_exists + protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object which allows the compositor + to inform the client of the preferred scale. + + + + + Destroy the fractional scale object. When this object is destroyed, + preferred_scale events will no longer be sent. + + + + + + Notification of a new preferred scale for this surface that the + compositor suggests that the client should use. + + The sent scale is the numerator of a fraction with a denominator of 120. + + + + + diff --git a/lib/renderers/wayland/registry.c b/lib/renderers/wayland/registry.c index 40861aa..e41ae0a 100644 --- a/lib/renderers/wayland/registry.c +++ b/lib/renderers/wayland/registry.c @@ -595,6 +595,10 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, co output->output = wl_output; wl_list_insert(&wayland->outputs, &output->link); wl_output_add_listener(wl_output, &output_listener, output); + } else if (strcmp(interface, "wp_fractional_scale_manager_v1") == 0) { + wayland->wfs_mgr = wl_registry_bind(registry, id, &wp_fractional_scale_manager_v1_interface, 1); + } else if (strcmp(interface, "wp_viewporter") == 0) { + wayland->viewporter = wl_registry_bind(registry, id, &wp_viewporter_interface, 1); } } @@ -660,6 +664,9 @@ bm_wl_registry_register(struct wayland *wayland) if (!wayland->input.keyboard || !(wayland->formats & (1 << WL_SHM_FORMAT_ARGB8888))) return false; + if (wayland->wfs_mgr && wayland->viewporter) + wayland->fractional_scaling = true; + set_repeat_info(&wayland->input, 40, 400); return true; } diff --git a/lib/renderers/wayland/viewporter.xml b/lib/renderers/wayland/viewporter.xml new file mode 100644 index 0000000..d1048d1 --- /dev/null +++ b/lib/renderers/wayland/viewporter.xml @@ -0,0 +1,180 @@ + + + + + Copyright © 2013-2016 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The global interface exposing surface cropping and scaling + capabilities is used to instantiate an interface extension for a + wl_surface object. This extended interface will then allow + cropping and scaling the surface contents, effectively + disconnecting the direct relationship between the buffer and the + surface size. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other objects, + wp_viewport objects included. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to + crop and scale its content. If the given wl_surface already has + a wp_viewport object associated, the viewport_exists + protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object, which allows the + client to specify the cropping and scaling of the surface + contents. + + This interface works with two concepts: the source rectangle (src_x, + src_y, src_width, src_height), and the destination size (dst_width, + dst_height). The contents of the source rectangle are scaled to the + destination size, and content outside the source rectangle is ignored. + This state is double-buffered, and is applied on the next + wl_surface.commit. + + The two parts of crop and scale state are independent: the source + rectangle, and the destination size. Initially both are unset, that + is, no scaling is applied. The whole of the current wl_buffer is + used as the source, and the surface size is as defined in + wl_surface.attach. + + If the destination size is set, it causes the surface size to become + dst_width, dst_height. The source (rectangle) is scaled to exactly + this size. This overrides whatever the attached wl_buffer size is, + unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + has no content and therefore no size. Otherwise, the size is always + at least 1x1 in surface local coordinates. + + If the source rectangle is set, it defines what area of the wl_buffer is + taken as the source. If the source rectangle is set and the destination + size is not set, then src_width and src_height must be integers, and the + surface size becomes the source rectangle size. This results in cropping + without scaling. If src_width or src_height are not integers and + destination size is not set, the bad_size protocol error is raised when + the surface state is applied. + + The coordinate transformations from buffer pixel coordinates up to + the surface-local coordinates happen in the following order: + 1. buffer_transform (wl_surface.set_buffer_transform) + 2. buffer_scale (wl_surface.set_buffer_scale) + 3. crop and scale (wp_viewport.set*) + This means, that the source rectangle coordinates of crop and scale + are given in the coordinates after the buffer transform and scale, + i.e. in the coordinates that would be the surface-local coordinates + if the crop and scale was not applied. + + If src_x or src_y are negative, the bad_value protocol error is raised. + Otherwise, if the source rectangle is partially or completely outside of + the non-NULL wl_buffer, then the out_of_buffer protocol error is raised + when the surface state is applied. A NULL wl_buffer does not raise the + out_of_buffer error. + + If the wl_surface associated with the wp_viewport is destroyed, + all wp_viewport requests except 'destroy' raise the protocol error + no_surface. + + If the wp_viewport object is destroyed, the crop and scale + state is removed from the wl_surface. The change will be applied + on the next wl_surface.commit. + + + + + The associated wl_surface's crop and scale state is removed. + The change is applied on the next wl_surface.commit. + + + + + + + + + + + + + Set the source rectangle of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If all of x, y, width and height are -1.0, the source rectangle is + unset instead. Any other set of values where width or height are zero + or negative, or x or y are negative, raise the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + + + + Set the destination size of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If width is -1 and height is -1, the destination size is unset + instead. Any other pair of values for width and height that + contains zero or negative values raises the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + diff --git a/lib/renderers/wayland/wayland.h b/lib/renderers/wayland/wayland.h index ab498f7..242a4d1 100644 --- a/lib/renderers/wayland/wayland.h +++ b/lib/renderers/wayland/wayland.h @@ -8,6 +8,8 @@ #include #include "wlr-layer-shell-unstable-v1.h" +#include "fractional-scale-v1.h" +#include "viewporter.h" #include "renderers/cairo_renderer.h" struct bm_menu; @@ -119,12 +121,13 @@ struct window { struct wl_surface *surface; struct wl_callback *frame_cb; struct zwlr_layer_surface_v1 *layer_surface; + struct wp_viewport *viewport_surface; struct wl_shm *shm; struct buffer buffers[2]; uint32_t width, height, max_height; uint32_t hmargin_size; float width_factor; - int32_t scale; + double scale; uint32_t displayed; struct wl_list link; enum bm_align align; @@ -167,6 +170,9 @@ struct wayland { struct input input; struct wl_list windows; uint32_t formats; + struct wp_fractional_scale_manager_v1 *wfs_mgr; + struct wp_viewporter *viewporter; + bool fractional_scaling; }; void bm_wl_repeat(struct wayland *wayland); diff --git a/lib/renderers/wayland/window.c b/lib/renderers/wayland/window.c index f7f4bc3..0043c2b 100644 --- a/lib/renderers/wayland/window.c +++ b/lib/renderers/wayland/window.c @@ -6,6 +6,7 @@ #include #include #include +#include static int set_cloexec_or_close(int fd) @@ -107,7 +108,7 @@ destroy_buffer(struct buffer *buffer) } static bool -create_buffer(struct wl_shm *shm, struct buffer *buffer, int32_t width, int32_t height, uint32_t format, int32_t scale) +create_buffer(struct wl_shm *shm, struct buffer *buffer, int32_t width, int32_t height, uint32_t format, double scale) { int fd = -1; struct wl_shm_pool *pool = NULL; @@ -188,10 +189,10 @@ next_buffer(struct window *window) if (!buffer) return NULL; - if (window->width * window->scale != buffer->width || window->height * window->scale != buffer->height) + if ((uint32_t) ceil(window->width * window->scale) != buffer->width || (uint32_t) ceil(window->height * window->scale) != buffer->height) destroy_buffer(buffer); - if (!buffer->buffer && !create_buffer(window->shm, buffer, window->width * window->scale, window->height * window->scale, WL_SHM_FORMAT_ARGB8888, window->scale)) + if (!buffer->buffer && !create_buffer(window->shm, buffer, ceil(window->width * window->scale), ceil(window->height * window->scale), WL_SHM_FORMAT_ARGB8888, window->scale)) return NULL; return buffer; @@ -258,17 +259,24 @@ bm_wl_window_render(struct window *window, struct wl_display *display, struct bm window->notify.render(&buffer->cairo, buffer->width, window->max_height, menu, &result); window->displayed = result.displayed; - if (window->height == result.height / window->scale) + if (window->height == (uint32_t) ceil(result.height / window->scale)) break; - window->height = result.height / window->scale; + window->height = ceil(result.height / window->scale); zwlr_layer_surface_v1_set_size(window->layer_surface, window->width, window->height); destroy_buffer(buffer); } - assert(window->width * window->scale == buffer->width); - assert(window->height * window->scale == buffer->height); - wl_surface_set_buffer_scale(window->surface, window->scale); + assert(ceil(window->width * window->scale) == buffer->width); + assert(ceil(window->height * window->scale) == buffer->height); + + if (window->wayland->fractional_scaling) { + assert(window->viewport_surface); + wp_viewport_set_destination(window->viewport_surface, window->width, window->height); + } else { + wl_surface_set_buffer_scale(window->surface, window->scale); + } + wl_surface_damage_buffer(window->surface, 0, 0, buffer->width, buffer->height); wl_surface_attach(window->surface, buffer->buffer, 0, 0); wl_surface_commit(window->surface); @@ -323,6 +331,21 @@ get_window_width(struct window *window) return width; } +static void +fractional_scale_preferred_scale( + void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, + uint32_t scale) +{ + (void)wp_fractional_scale_v1; + struct window *window = data; + + window->scale = (double)scale / 120; +} + +static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { + .preferred_scale = fractional_scale_preferred_scale, +}; + static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { .configure = layer_surface_configure, .closed = layer_surface_closed, @@ -393,6 +416,17 @@ bm_wl_window_create(struct window *window, struct wl_display *display, struct wl { assert(window); + struct wayland *wayland = window->wayland; + + if (wayland->fractional_scaling) { + assert(wayland->wfs_mgr && wayland->viewporter); + + struct wp_fractional_scale_v1 *wfs_surf = wp_fractional_scale_manager_v1_get_fractional_scale(wayland->wfs_mgr, surface); + wp_fractional_scale_v1_add_listener( + wfs_surf, &fractional_scale_listener, window); + window->viewport_surface = wp_viewporter_get_viewport(wayland->viewporter, surface); + } + enum zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; if (layer_shell && (window->layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell, surface, output, layer, "menu"))) { zwlr_layer_surface_v1_add_listener(window->layer_surface, &layer_surface_listener, window);