diff --git a/inc/displ.h b/inc/displ.h index 6abfbbe3..1f7f6bb6 100644 --- a/inc/displ.h +++ b/inc/displ.h @@ -2,6 +2,7 @@ #define DISPL_H #include +#include enum ConfigState { IDLE = 0, @@ -23,6 +24,8 @@ struct Displ { char *interface; uint32_t output_manager_version; + bool have_fractional_scale_v1; + enum ConfigState config_state; }; diff --git a/inc/head.h b/inc/head.h index 764f0c77..709bdf05 100644 --- a/inc/head.h +++ b/inc/head.h @@ -6,6 +6,7 @@ #include #include +#include "displ.h" #include "mode.h" #include "wlr-output-management-unstable-v1.h" @@ -26,6 +27,8 @@ struct HeadState { struct Head { + struct Displ *displ; + struct zwlr_output_head_v1 *zwlr_head; struct zwlr_output_configuration_head_v1 *zwlr_config_head; @@ -67,6 +70,10 @@ bool head_matches_name_desc(const void *head, const void *name_desc); bool head_name_desc_matches_head(const void *name_desc, const void *head); +wl_fixed_t head_get_fixed_scale(const struct Head *head, double scale); + +int32_t head_get_scaled_length(const struct Head *head, int32_t length, wl_fixed_t fixed_scale); + wl_fixed_t head_auto_scale(struct Head *head, double min, double max); void head_scaled_dimensions(struct Head *head); diff --git a/pro/fractional-scale-v1.xml b/pro/fractional-scale-v1.xml new file mode 100644 index 00000000..350bfc01 --- /dev/null +++ b/pro/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/src/head.c b/src/head.c index ebf2e211..418d007e 100644 --- a/src/head.c +++ b/src/head.c @@ -141,15 +141,52 @@ bool head_matches_name_desc_exact(const void *h, const void *n) { (head->description && strcmp(head->description, name_desc) == 0); } +wl_fixed_t head_get_fixed_scale(const struct Head *head, double scale) { + // computes a scale value that is appropriate for putting into `zwlr_output_configuration_head_v1_set_scale` + + wl_fixed_t fixed_scale = wl_fixed_from_double(scale); + + if (head && head->displ && head->displ->have_fractional_scale_v1) { + /* + * The following ensures that the scale which the compositor reports *after* we applied the layout is the same + * that was put in. This prevents repeat "flickering" of the scale value, causing way-displays to continuously + * change the scale. + * + * Note that this is currently only *observed* behavior in a Sway revision with fractional-scale-v1, which is + * where the factor 120 comes from. It does not occur on Sway 1.8.1 (which doesn't yet have support for the + * fractional scaling protocol). + * + * TODO: Can we "prove" that this is the right thing to do by looking at protocol specifications? + */ + fixed_scale = floor((double)fixed_scale / 256 * 120) * ((double)256 / 120) + 0.5; + } + + return fixed_scale; +} + +int32_t head_get_scaled_length(const struct Head *head, int32_t length, wl_fixed_t fixed_scale) { + // scales a (pixel) length by fixed_scale + + double base = 256.0; + + if (head && head->displ && head->displ->have_fractional_scale_v1) { + // see comment in `head_get_fixed_scale()` + base = 120.0; + fixed_scale = (double)fixed_scale / 256 * 120 + 0.5; + } + + return floor(length * base / fixed_scale); +} + wl_fixed_t head_auto_scale(struct Head *head, double min, double max) { if (!head || !head->desired.mode) { - return wl_fixed_from_int(1); + return head_get_fixed_scale(head, 1.0); } // average dpi double dpi = mode_dpi(head->desired.mode); if (dpi == 0) { - return wl_fixed_from_int(1); + return head_get_fixed_scale(head, 1.0); } // round the dpi to the nearest 12, so that we get a nice even wl_fixed_t @@ -170,7 +207,7 @@ wl_fixed_t head_auto_scale(struct Head *head, double min, double max) { } // 96dpi approximately correct for older monitors and became the convention for 1:1 scaling - return wl_fixed_from_double((double) dpi_quantized / 96); + return head_get_fixed_scale(head, (double) dpi_quantized / 96); } void head_scaled_dimensions(struct Head *head) { @@ -186,9 +223,8 @@ void head_scaled_dimensions(struct Head *head) { head->scaled.height = head->desired.mode->width; } - // wayland truncates when calculating size so we do the same - head->scaled.height = (int32_t)((double)head->scaled.height * 256 / head->desired.scale); - head->scaled.width = (int32_t)((double)head->scaled.width * 256 / head->desired.scale); + head->scaled.height = head_get_scaled_length(head, head->scaled.height, head->desired.scale); + head->scaled.width = head_get_scaled_length(head, head->scaled.width, head->desired.scale); } struct Mode *head_find_mode(struct Head *head) { diff --git a/src/layout.c b/src/layout.c index e8e43be0..e918ab57 100644 --- a/src/layout.c +++ b/src/layout.c @@ -182,7 +182,7 @@ void desire_scale(struct Head *head) { // all scaling disabled if (cfg->scaling == OFF) { - head->desired.scale = wl_fixed_from_int(1); + head->desired.scale = head_get_fixed_scale(head, 1.0); return; } @@ -191,7 +191,7 @@ void desire_scale(struct Head *head) { for (struct SList *i = cfg->user_scales; i; i = i->nex) { user_scale = (struct UserScale*)i->val; if (head_matches_name_desc(head, user_scale->name_desc)) { - head->desired.scale = wl_fixed_from_double(user_scale->scale); + head->desired.scale = head_get_fixed_scale(head, user_scale->scale); return; } } @@ -201,7 +201,7 @@ void desire_scale(struct Head *head) { head->desired.scale = head_auto_scale(head, cfg->auto_scale_min, cfg->auto_scale_max); } else { - head->desired.scale = wl_fixed_from_int(1); + head->desired.scale = head_get_fixed_scale(head, 1.0); } } diff --git a/src/listener_output_manager.c b/src/listener_output_manager.c index dd364c13..5587bcae 100644 --- a/src/listener_output_manager.c +++ b/src/listener_output_manager.c @@ -17,6 +17,7 @@ static void head(void *data, struct Displ *displ = data; struct Head *head = calloc(1, sizeof(struct Head)); + head->displ = displ; head->zwlr_head = zwlr_output_head_v1; slist_append(&heads, head); diff --git a/src/listener_registry.c b/src/listener_registry.c index fb557db7..03fe5932 100644 --- a/src/listener_registry.c +++ b/src/listener_registry.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -9,6 +9,7 @@ #include "displ.h" #include "log.h" #include "process.h" +#include "fractional-scale-v1.h" #include "wlr-output-management-unstable-v1.h" // Displ data @@ -18,28 +19,29 @@ static void global(void *data, uint32_t name, const char *interface, uint32_t version) { - - // only register for WLR output manager events - if (strcmp(interface, zwlr_output_manager_v1_interface.name) != 0) - return; - struct Displ *displ = data; - displ->name = name; - displ->interface = strdup(interface); - if (version < ZWLR_OUTPUT_MANAGER_V1_VERSION_MIN) { - log_error("\nwlr-output-management version %d found, minimum %d required, exiting. Consider upgrading your compositor.", version, ZWLR_OUTPUT_MANAGER_V1_VERSION_MIN); - wd_exit(EXIT_FAILURE); - } else if (version < ZWLR_OUTPUT_MANAGER_V1_VERSION) { - log_warn("\nwlr-output-management version %d found; %d required for full functionality. Consider upgrading your compositor.", version, ZWLR_OUTPUT_MANAGER_V1_VERSION); - displ->output_manager_version = ZWLR_OUTPUT_MANAGER_V1_VERSION_MIN; - } else { - displ->output_manager_version = ZWLR_OUTPUT_MANAGER_V1_VERSION; - } + if (strcmp(interface, zwlr_output_manager_v1_interface.name) == 0) { + displ->name = name; + displ->interface = strdup(interface); + + if (version < ZWLR_OUTPUT_MANAGER_V1_VERSION_MIN) { + log_error("\nwlr-output-management version %d found, minimum %d required, exiting. Consider upgrading your compositor.", version, ZWLR_OUTPUT_MANAGER_V1_VERSION_MIN); + wd_exit(EXIT_FAILURE); + } else if (version < ZWLR_OUTPUT_MANAGER_V1_VERSION) { + log_warn("\nwlr-output-management version %d found; %d required for full functionality. Consider upgrading your compositor.", version, ZWLR_OUTPUT_MANAGER_V1_VERSION); + displ->output_manager_version = ZWLR_OUTPUT_MANAGER_V1_VERSION_MIN; + } else { + displ->output_manager_version = ZWLR_OUTPUT_MANAGER_V1_VERSION; + } - displ->output_manager = wl_registry_bind(wl_registry, name, &zwlr_output_manager_v1_interface, displ->output_manager_version); + displ->output_manager = wl_registry_bind(wl_registry, name, &zwlr_output_manager_v1_interface, displ->output_manager_version); - zwlr_output_manager_v1_add_listener(displ->output_manager, output_manager_listener(), displ); + zwlr_output_manager_v1_add_listener(displ->output_manager, output_manager_listener(), displ); + } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { + displ->have_fractional_scale_v1 = true; + log_debug("compositor supports the fractional-scale protocol, version %d", wp_fractional_scale_manager_v1_interface.version); + } } static void global_remove(void *data,