Non-resizable window with initially requested physical size uses scale 1 for size constraints
Description
When window is created, winit converts its size to physical pixels by assuming scale factor to be 1.0:
https://github.com/rust-windowing/winit/blob/9cbce055d35d1270e7e4cc43ba4c81fffa6d1bb9/src/platform_impl/linux/wayland/window/state.rs#L212-L213
It never corrects this assumption leading to incorrect window sizes.
An easy way to reproduce the issue is to create a non-resizeable window with fixed physical size. A full repro is available here: https://github.com/GoldsteinE/winit/blob/scale-bug-repro/examples/bug.rs, run as cargo run --example bug. On Wayland displays with non-1.0 scale the window will have incorrect physical size:
[examples/bug.rs:51:17] size = PhysicalSize {
width: 400,
height: 400,
}
[examples/bug.rs:51:17] size = PhysicalSize {
width: 800,
height: 800,
}
I found this bug while debugging an issue with egui/eframe, my full writeup on it is here: https://github.com/emilk/egui/issues/7095#issuecomment-2920545377
Debugging output
Pretty long
[ 564291.635] -> wl_display#1.get_registry(new id wl_registry#2)
[ 564291.653] -> wl_display#1.sync(new id wl_callback#3)
[ 564292.122] {Display Queue} wl_display#1.delete_id(3)
[ 564292.131] wl_registry#2.global(1, "wl_shm", 2)
[ 564292.174] wl_registry#2.global(2, "zwp_linux_dmabuf_v1", 4)
[ 564292.179] wl_registry#2.global(3, "wp_linux_drm_syncobj_manager_v1", 1)
[ 564292.182] wl_registry#2.global(4, "wl_compositor", 6)
[ 564292.187] wl_registry#2.global(5, "wl_subcompositor", 1)
[ 564292.195] wl_registry#2.global(6, "wl_data_device_manager", 3)
[ 564292.200] wl_registry#2.global(7, "zwlr_gamma_control_manager_v1", 1)
[ 564292.206] wl_registry#2.global(8, "zxdg_output_manager_v1", 3)
[ 564292.212] wl_registry#2.global(9, "ext_idle_notifier_v1", 2)
[ 564292.217] wl_registry#2.global(10, "zwp_idle_inhibit_manager_v1", 1)
[ 564292.222] wl_registry#2.global(11, "zwlr_layer_shell_v1", 4)
[ 564292.227] wl_registry#2.global(12, "xdg_wm_base", 5)
[ 564292.234] wl_registry#2.global(13, "zwp_tablet_manager_v2", 1)
[ 564292.240] wl_registry#2.global(14, "org_kde_kwin_server_decoration_manager", 1)
[ 564292.245] wl_registry#2.global(15, "zxdg_decoration_manager_v1", 1)
[ 564292.251] wl_registry#2.global(16, "zwp_relative_pointer_manager_v1", 1)
[ 564292.256] wl_registry#2.global(17, "zwp_pointer_constraints_v1", 1)
[ 564292.263] wl_registry#2.global(18, "wp_presentation", 2)
[ 564292.270] wl_registry#2.global(19, "wp_alpha_modifier_v1", 1)
[ 564292.276] wl_registry#2.global(20, "zwlr_output_manager_v1", 4)
[ 564292.283] wl_registry#2.global(21, "zwlr_output_power_manager_v1", 1)
[ 564292.289] wl_registry#2.global(22, "zwp_input_method_manager_v2", 1)
[ 564292.295] wl_registry#2.global(23, "zwp_text_input_manager_v3", 1)
[ 564292.300] wl_registry#2.global(24, "ext_foreign_toplevel_list_v1", 1)
[ 564292.306] wl_registry#2.global(25, "zwlr_foreign_toplevel_manager_v1", 3)
[ 564292.311] wl_registry#2.global(26, "ext_session_lock_manager_v1", 1)
[ 564292.318] wl_registry#2.global(27, "wp_drm_lease_device_v1", 1)
[ 564292.323] wl_registry#2.global(28, "zwlr_export_dmabuf_manager_v1", 1)
[ 564292.328] wl_registry#2.global(29, "zwlr_screencopy_manager_v1", 3)
[ 564292.333] wl_registry#2.global(30, "ext_image_copy_capture_manager_v1", 1)
[ 564292.338] wl_registry#2.global(31, "ext_output_image_capture_source_manager_v1", 1)
[ 564292.343] wl_registry#2.global(32, "zwlr_data_control_manager_v1", 2)
[ 564292.348] wl_registry#2.global(33, "ext_data_control_manager_v1", 1)
[ 564292.355] wl_registry#2.global(34, "wp_security_context_manager_v1", 1)
[ 564292.362] wl_registry#2.global(35, "wp_viewporter", 1)
[ 564292.369] wl_registry#2.global(36, "wp_single_pixel_buffer_manager_v1", 1)
[ 564292.376] wl_registry#2.global(37, "wp_content_type_manager_v1", 1)
[ 564292.383] wl_registry#2.global(38, "wp_fractional_scale_manager_v1", 1)
[ 564292.389] wl_registry#2.global(39, "wp_tearing_control_manager_v1", 1)
[ 564292.396] wl_registry#2.global(40, "zxdg_exporter_v1", 1)
[ 564292.402] wl_registry#2.global(41, "zxdg_importer_v1", 1)
[ 564292.408] wl_registry#2.global(42, "zxdg_exporter_v2", 1)
[ 564292.414] wl_registry#2.global(43, "zxdg_importer_v2", 1)
[ 564292.419] wl_registry#2.global(44, "xdg_activation_v1", 1)
[ 564292.426] wl_registry#2.global(45, "wp_cursor_shape_manager_v1", 1)
[ 564292.431] wl_registry#2.global(46, "zwp_virtual_keyboard_manager_v1", 1)
[ 564292.437] wl_registry#2.global(47, "zwlr_virtual_pointer_manager_v1", 2)
[ 564292.443] wl_registry#2.global(48, "zwp_keyboard_shortcuts_inhibit_manager_v1", 1)
[ 564292.448] wl_registry#2.global(49, "zwp_pointer_gestures_v1", 3)
[ 564292.453] wl_registry#2.global(50, "ext_transient_seat_manager_v1", 1)
[ 564292.458] wl_registry#2.global(51, "wl_seat", 9)
[ 564292.463] wl_registry#2.global(53, "zwp_primary_selection_device_manager_v1", 1)
[ 564292.468] wl_registry#2.global(54, "wl_output", 4)
[ 564292.473] wl_callback#3.done(30897)
[ 564292.563] -> wl_registry#2.bind(4, "wl_compositor", 6, new id [unknown]#3)
[ 564292.580] -> wl_registry#2.bind(5, "wl_subcompositor", 1, new id [unknown]#4)
[ 564292.590] -> wl_registry#2.bind(54, "wl_output", 4, new id [unknown]#5)
[ 564292.606] -> wl_registry#2.bind(8, "zxdg_output_manager_v1", 3, new id [unknown]#6)
[ 564292.635] -> zxdg_output_manager_v1#6.get_xdg_output(new id zxdg_output_v1#7, wl_output#5)
[ 564292.656] -> wl_registry#2.bind(51, "wl_seat", 7, new id [unknown]#8)
[ 564292.687] -> wl_registry#2.bind(38, "wp_fractional_scale_manager_v1", 1, new id [unknown]#9)
[ 564292.697] -> wl_registry#2.bind(35, "wp_viewporter", 1, new id [unknown]#10)
[ 564292.705] -> wl_registry#2.bind(1, "wl_shm", 1, new id [unknown]#11)
[ 564292.733] -> wl_shm#11.create_pool(new id wl_shm_pool#12, fd 8, 2)
[ 564292.751] -> wl_registry#2.bind(12, "xdg_wm_base", 5, new id [unknown]#13)
[ 564292.759] -> wl_registry#2.bind(15, "zxdg_decoration_manager_v1", 1, new id [unknown]#14)
[ 564292.768] -> wl_registry#2.bind(44, "xdg_activation_v1", 1, new id [unknown]#15)
[ 564292.785] -> wl_registry#2.bind(23, "zwp_text_input_manager_v3", 1, new id [unknown]#16)
[ 564292.795] -> wl_registry#2.bind(16, "zwp_relative_pointer_manager_v1", 1, new id [unknown]#17)
[ 564292.803] -> wl_registry#2.bind(17, "zwp_pointer_constraints_v1", 1, new id [unknown]#18)
[ 564292.813] -> wl_display#1.sync(new id wl_callback#19)
[ 564292.906] {Display Queue} wl_display#1.delete_id(19)
[ 564292.909] wl_output#5.geometry(0, 0, 340, 220, 0, "BOE", "0x0BC9", 0)
[ 564292.917] wl_output#5.mode(1, 2560, 1600, 165000)
[ 564292.920] wl_output#5.scale(2)
[ 564292.923] wl_output#5.name("eDP-1")
[ 564292.927] wl_output#5.description("BOE 0x0BC9 (eDP-1)")
[ 564292.930] wl_output#5.done()
[ 564292.935] zxdg_output_v1#7.name("eDP-1")
[ 564292.939] zxdg_output_v1#7.description("BOE 0x0BC9 (eDP-1)")
[ 564292.943] zxdg_output_v1#7.logical_position(0, 0)
[ 564292.946] zxdg_output_v1#7.logical_size(1280, 800)
[ 564292.950] wl_output#5.done()
[ 564292.953] wl_seat#8.name("seat0")
[ 564292.957] wl_seat#8.capabilities(3)
[ 564292.961] wl_shm#11.format(0)
[ 564292.965] wl_shm#11.format(1)
[ 564292.970] wl_shm#11.format(875709016)
[ 564292.972] wl_shm#11.format(875708993)
[ 564292.980] wl_shm#11.format(875710274)
[ 564292.983] wl_shm#11.format(842094674)
[ 564292.985] wl_shm#11.format(842088786)
[ 564292.989] wl_shm#11.format(892426322)
[ 564292.992] wl_shm#11.format(892420434)
[ 564292.996] wl_shm#11.format(909199186)
[ 564292.999] wl_shm#11.format(808665688)
[ 564293.004] wl_shm#11.format(808665665)
[ 564293.009] wl_shm#11.format(1211384408)
[ 564293.012] wl_shm#11.format(1211384385)
[ 564293.016] wl_shm#11.format(942948952)
[ 564293.021] wl_shm#11.format(942948929)
[ 564293.023] wl_callback#19.done(30897)
[ 564293.063] -> wl_seat#8.get_keyboard(new id wl_keyboard#19)
[ 564294.755] -> zwp_text_input_manager_v3#16.get_text_input(new id zwp_text_input_v3#20, wl_seat#8)
[ 564294.790] -> wl_compositor#3.create_surface(new id wl_surface#21)
[ 564294.805] -> wp_viewporter#10.get_viewport(new id wp_viewport#22, wl_surface#21)
[ 564294.820] -> wl_seat#8.get_pointer(new id wl_pointer#23)
[ 564294.835] -> wl_registry#2.bind(45, "wp_cursor_shape_manager_v1", 1, new id [unknown]#24)
[ 564294.849] -> wp_cursor_shape_manager_v1#24.get_pointer(new id wp_cursor_shape_device_v1#25, wl_pointer#23)
[ 564294.870] -> zwp_relative_pointer_manager_v1#17.get_relative_pointer(new id zwp_relative_pointer_v1#26, wl_pointer#23)
[ 564294.991] -> wl_compositor#3.create_surface(new id wl_surface#27)
[ 564295.007] -> xdg_wm_base#13.get_xdg_surface(new id xdg_surface#28, wl_surface#27)
[ 564295.019] -> xdg_surface#28.get_toplevel(new id xdg_toplevel#29)
[ 564295.029] -> zxdg_decoration_manager_v1#14.get_toplevel_decoration(new id zxdg_toplevel_decoration_v1#30, xdg_toplevel#29)
[ 564295.045] -> zxdg_toplevel_decoration_v1#30.set_mode(2)
[ 564295.055] -> wp_viewporter#10.get_viewport(new id wp_viewport#31, wl_surface#27)
[ 564295.064] -> wp_fractional_scale_manager_v1#9.get_fractional_scale(new id wp_fractional_scale_v1#32, wl_surface#27)
[ 564295.076] -> wl_compositor#3.create_region(new id wl_region#33)
[ 564295.090] -> wl_region#33.add(0, 0, 2147483647, 2147483647)
[ 564295.097] -> wl_surface#27.set_opaque_region(wl_region#33)
[ 564295.102] -> wl_region#33.destroy()
[ 564295.113] -> xdg_toplevel#29.set_title("bug")
[ 564295.125] -> xdg_toplevel#29.set_min_size(2, 1)
[ 564295.130] -> xdg_toplevel#29.set_max_size(0, 0)
[ 564295.134] -> xdg_toplevel#29.set_min_size(400, 400)
[ 564295.139] -> xdg_toplevel#29.set_max_size(400, 400)
[ 564295.149] -> wl_surface#27.commit()
[ 564295.162] -> wl_display#1.sync(new id wl_callback#34)
[ 564295.270] {Display Queue} wl_display#1.delete_id(33)
[ 564295.274] {Display Queue} wl_display#1.delete_id(34)
[ 564295.277] wl_keyboard#19.keymap(1, fd 10, 72284)
[ 564295.285] wl_keyboard#19.repeat_info(25, 600)
[ 564295.288] xdg_wm_base#13.ping(30898)
[ 564295.293] wl_callback#34.done(30899)
[ 564295.297] xdg_toplevel#29.wm_capabilities(array[8])
[ 564295.303] xdg_toplevel#29.configure(0, 0, array[0])
[ 564295.307] zxdg_toplevel_decoration_v1#30.configure(2)
[ 564295.311] xdg_surface#28.configure(30899)
[ 564296.392] -> xdg_wm_base#13.pong(30898)
[ 564296.422] -> xdg_surface#28.ack_configure(30899)
[ 564296.436] -> wl_compositor#3.create_region(new id wl_region#34)
[ 564296.444] -> wl_region#34.add(0, 0, 2147483647, 2147483647)
[ 564296.450] -> wl_surface#27.set_opaque_region(wl_region#34)
[ 564296.455] -> wl_region#34.destroy()
[ 564296.462] -> xdg_surface#28.set_window_geometry(0, 0, 400, 400)
[ 564296.468] -> wp_viewport#31.set_destination(400, 400)
[examples/bug.rs:51:17] size = PhysicalSize {
width: 400,
height: 400,
}
[ 564296.520] -> wl_surface#27.frame(new id wl_callback#33)
[ 564296.535] -> wl_display#1.get_registry(new id wl_registry#35)
[ 564296.542] -> wl_display#1.sync(new id wl_callback#36)
[ 564296.589] {Display Queue} wl_display#1.delete_id(34)
[ 564296.592] {Display Queue} wl_display#1.delete_id(36)
[ 564296.595] wl_registry#35.global(1, "wl_shm", 2)
[ 564296.609] wl_registry#35.global(2, "zwp_linux_dmabuf_v1", 4)
[ 564296.614] wl_registry#35.global(3, "wp_linux_drm_syncobj_manager_v1", 1)
[ 564296.619] wl_registry#35.global(4, "wl_compositor", 6)
[ 564296.624] wl_registry#35.global(5, "wl_subcompositor", 1)
[ 564296.629] wl_registry#35.global(6, "wl_data_device_manager", 3)
[ 564296.634] wl_registry#35.global(7, "zwlr_gamma_control_manager_v1", 1)
[ 564296.639] wl_registry#35.global(8, "zxdg_output_manager_v1", 3)
[ 564296.645] wl_registry#35.global(9, "ext_idle_notifier_v1", 2)
[ 564296.650] wl_registry#35.global(10, "zwp_idle_inhibit_manager_v1", 1)
[ 564296.655] wl_registry#35.global(11, "zwlr_layer_shell_v1", 4)
[ 564296.660] wl_registry#35.global(12, "xdg_wm_base", 5)
[ 564296.665] wl_registry#35.global(13, "zwp_tablet_manager_v2", 1)
[ 564296.670] wl_registry#35.global(14, "org_kde_kwin_server_decoration_manager", 1)
[ 564296.675] wl_registry#35.global(15, "zxdg_decoration_manager_v1", 1)
[ 564296.680] wl_registry#35.global(16, "zwp_relative_pointer_manager_v1", 1)
[ 564296.685] wl_registry#35.global(17, "zwp_pointer_constraints_v1", 1)
[ 564296.690] wl_registry#35.global(18, "wp_presentation", 2)
[ 564296.696] wl_registry#35.global(19, "wp_alpha_modifier_v1", 1)
[ 564296.701] wl_registry#35.global(20, "zwlr_output_manager_v1", 4)
[ 564296.706] wl_registry#35.global(21, "zwlr_output_power_manager_v1", 1)
[ 564296.711] wl_registry#35.global(22, "zwp_input_method_manager_v2", 1)
[ 564296.716] wl_registry#35.global(23, "zwp_text_input_manager_v3", 1)
[ 564296.721] wl_registry#35.global(24, "ext_foreign_toplevel_list_v1", 1)
[ 564296.726] wl_registry#35.global(25, "zwlr_foreign_toplevel_manager_v1", 3)
[ 564296.731] wl_registry#35.global(26, "ext_session_lock_manager_v1", 1)
[ 564296.736] wl_registry#35.global(27, "wp_drm_lease_device_v1", 1)
[ 564296.742] wl_registry#35.global(28, "zwlr_export_dmabuf_manager_v1", 1)
[ 564296.747] wl_registry#35.global(29, "zwlr_screencopy_manager_v1", 3)
[ 564296.752] wl_registry#35.global(30, "ext_image_copy_capture_manager_v1", 1)
[ 564296.757] wl_registry#35.global(31, "ext_output_image_capture_source_manager_v1", 1)
[ 564296.763] wl_registry#35.global(32, "zwlr_data_control_manager_v1", 2)
[ 564296.768] wl_registry#35.global(33, "ext_data_control_manager_v1", 1)
[ 564296.773] wl_registry#35.global(34, "wp_security_context_manager_v1", 1)
[ 564296.778] wl_registry#35.global(35, "wp_viewporter", 1)
[ 564296.783] wl_registry#35.global(36, "wp_single_pixel_buffer_manager_v1", 1)
[ 564296.788] wl_registry#35.global(37, "wp_content_type_manager_v1", 1)
[ 564296.793] wl_registry#35.global(38, "wp_fractional_scale_manager_v1", 1)
[ 564296.799] wl_registry#35.global(39, "wp_tearing_control_manager_v1", 1)
[ 564296.803] wl_registry#35.global(40, "zxdg_exporter_v1", 1)
[ 564296.809] wl_registry#35.global(41, "zxdg_importer_v1", 1)
[ 564296.814] wl_registry#35.global(42, "zxdg_exporter_v2", 1)
[ 564296.818] wl_registry#35.global(43, "zxdg_importer_v2", 1)
[ 564296.823] wl_registry#35.global(44, "xdg_activation_v1", 1)
[ 564296.829] wl_registry#35.global(45, "wp_cursor_shape_manager_v1", 1)
[ 564296.834] wl_registry#35.global(46, "zwp_virtual_keyboard_manager_v1", 1)
[ 564296.839] wl_registry#35.global(47, "zwlr_virtual_pointer_manager_v1", 2)
[ 564296.844] wl_registry#35.global(48, "zwp_keyboard_shortcuts_inhibit_manager_v1", 1)
[ 564296.849] wl_registry#35.global(49, "zwp_pointer_gestures_v1", 3)
[ 564296.854] wl_registry#35.global(50, "ext_transient_seat_manager_v1", 1)
[ 564296.859] wl_registry#35.global(51, "wl_seat", 9)
[ 564296.864] wl_registry#35.global(53, "zwp_primary_selection_device_manager_v1", 1)
[ 564296.870] wl_registry#35.global(54, "wl_output", 4)
[ 564296.874] wl_callback#36.done(30899)
[ 564296.886] -> wl_registry#35.bind(1, "wl_shm", 1, new id [unknown]#36)
[ 564296.937] -> wl_shm#36.create_pool(new id wl_shm_pool#34, fd 12, 1048576)
[ 564296.949] -> wl_shm_pool#34.create_buffer(new id wl_buffer#37, 0, 400, 400, 1600, 1)
[ 564296.966] -> wl_shm#36.create_pool(new id wl_shm_pool#38, fd 14, 1048576)
[ 564296.972] -> wl_shm_pool#38.create_buffer(new id wl_buffer#39, 0, 400, 400, 1600, 1)
[ 564297.762] -> wl_surface#27.attach(wl_buffer#39, 0, 0)
[ 564297.769] -> wl_surface#27.damage_buffer(0, 0, 400, 400)
[ 564297.774] -> wl_surface#27.commit()
[ 564299.368] {Display Queue} wl_display#1.delete_id(33)
[ 564299.377] wp_fractional_scale_v1#32.preferred_scale(240)
[ 564299.387] wl_surface#27.preferred_buffer_scale(2)
[ 564299.392] wl_keyboard#19.enter(30903, wl_surface#27, array[0])
[ 564299.398] wl_keyboard#19.modifiers(30904, 0, 0, 0, 0)
[ 564299.402] wl_callback#33.done(386519985)
[ 564299.409] xdg_toplevel#29.configure(400, 400, array[4])
[ 564299.413] xdg_surface#28.configure(30900)
[ 564299.474] -> xdg_surface#28.ack_configure(30900)
[examples/bug.rs:51:17] size = PhysicalSize {
width: 800,
height: 800,
}
[ 564299.527] -> wl_surface#27.frame(new id wl_callback#33)
[ 564299.541] -> wl_buffer#37.destroy()
[ 564299.554] -> wl_shm_pool#34.resize(4194304)
[ 564299.577] -> wl_shm_pool#34.create_buffer(new id wl_buffer#40, 0, 800, 800, 3200, 1)
[ 564302.845] wl_shm#36.format(0)
[ 564302.856] wl_shm#36.format(1)
[ 564302.859] wl_shm#36.format(875709016)
[ 564302.863] wl_shm#36.format(875708993)
[ 564302.866] wl_shm#36.format(875710274)
[ 564302.871] wl_shm#36.format(842094674)
[ 564302.874] wl_shm#36.format(842088786)
[ 564302.878] wl_shm#36.format(892426322)
[ 564302.881] wl_shm#36.format(892420434)
[ 564302.885] wl_shm#36.format(909199186)
[ 564302.888] wl_shm#36.format(808665688)
[ 564302.891] wl_shm#36.format(808665665)
[ 564302.894] wl_shm#36.format(1211384408)
[ 564302.897] wl_shm#36.format(1211384385)
[ 564302.901] wl_shm#36.format(942948952)
[ 564302.904] wl_shm#36.format(942948929)
[ 564302.907] wl_buffer#39.release()
[ 564302.930] -> wl_surface#27.attach(wl_buffer#40, 0, 0)
[ 564302.937] -> wl_surface#27.damage_buffer(0, 0, 800, 800)
[ 564302.942] -> wl_surface#27.commit()
[ 564304.194] {Display Queue} wl_display#1.delete_id(37)
[ 564304.201] wl_surface#27.enter(wl_output#5)
[ 564304.209] wl_surface#27.preferred_buffer_transform(0)
[ 564304.497] {Display Queue} wl_display#1.delete_id(33)
[ 564304.500] wl_callback#33.done(386519990)
Window isn't shown unless you draw
- [x] I understand that windows aren't shown on Wayland unless I draw and present to them.
Winit version
0.30.11
But everything is correct according to the log. You've created a window of logical size 400x400, then you've got resize to physical size of 800x800 with scale of 2 meaning that you have a logical size of 400x400?
When window is created, winit converts its size to physical pixels by assuming scale factor to be 1.0:
This is wrong if you simply look in the code you've linked, it converts to logical but not initial size, initial size is below and not converted unless we get initial configure.
Also, from the log it seems that the compositor delays the scaling event, thus leading to the issue you observe and winit only uses original sizes for the initial configure. I'm not sure it makes much sense to account for something that is compositor bug, since it's stated in the protocol(xdg one) that scaling should be send along the initial configure.
You've created a window of logical size
I’ve created a window of physical size 400x400.
.with_inner_size(PhysicalSize::new(400.0, 400.0))
https://github.com/GoldsteinE/winit/blob/scale-bug-repro/examples/bug.rs#L30
winit incorrectly converted that to logical size 400x400, created the window, and then it was resized to physical size 800x800. At no place in my code I set logical size to be 400x400.
Also, from the log it seems that the compositor delays the scaling event, thus leading to the issue you observe and winit only uses original sizes for the initial configure.
Even if the scale was sent with the initial configure, scale can change at runtime. You can also reproduce the issue by creating a window while scale is set to 1.0 and then changing the scale to something else. This can also happen when window is moved between displays with different scales.
I’m also not sure if the scale sent with initial configure is relevant here, because winit ignores it and uses the hardcoded scale of 1.0 to convert from physical size to logical size on window creation. UPD: I’ve also reproduced the issue on Weston.
This is wrong if you simply look in the code you've linked, it converts to logical but not initial size, initial size is below and not converted unless we get initial configure.
.set_resizeable(false) uses self.size (which was incorrectly converted with scale factor 1.0) and not self.initial_size:
https://github.com/rust-windowing/winit/blob/9cbce055d35d1270e7e4cc43ba4c81fffa6d1bb9/src/platform_impl/linux/wayland/window/state.rs#L513-L514
The issue is more complex then what you describe and it has nothing to do with winit converting to logical sizes in the places you mention other than as you've noticed the min/max inner size.
I'd try to give some explanation on that matter, but generally. On Wayland, not-resizable window is created by setting min/max sizes to the same value when creating a window. During window creation time the scale is always 1, since everything is logical/real scale is unknown/etc.
Setting resizable to false at the same time, makes compositor to emit configure with the size of logical 400px and winit will obey and use that size as logical overriding your physical preference since compositor communicated its preference, since compositor decided that you must use logical size of 400x400 you'll get physical size of 800x800, because we've obeyed the compositor here. The client though, can resize back to the physical size they want during handling of resize event, if compositor resized them to a bigger value than they wanted to.
Now in this particular case, winit may have a following heuristic:
if the configure's logical size matches the min/max size, and initial size was physical, then winit will try to keep the window of the physical size when the scale adjusts, yes, your window will resize on screen due to that, since wayland is logical, but you want to preserve physical size, which is not what non-resizable really is, but tbf, its definition is rather vague and more like compositor shouldn't provide resize options for this window, but client itself can resize it.
And yes, initial scale won't really help, since the issue is solely resizable(false) thing, if you didn't have it, then everything would be preserved, but because min/max sizes are logical on the protocol level there would need to be dynamic readjustments based on some heuristics (Note that the result would be that you'd always resize yourself to a smaller window on a monitor).
Setting resizable to false at the same time, makes compositor to emit configure with the size of logical 400px and winit will obey and use that size as logical overriding your physical preference since compositor communicated its preference
Consider this trace from Weston (produced by running the same code):
Still a long trace
[ 394932.496] -> wl_display#1.get_registry(new id wl_registry#2)
[ 394932.525] -> wl_display#1.sync(new id wl_callback#3)
[ 394932.676] {Display Queue} wl_display#1.delete_id(3)
[ 394932.692] wl_registry#2.global(1, "wl_compositor", 5)
[ 394932.726] wl_registry#2.global(2, "wl_subcompositor", 1)
[ 394932.733] wl_registry#2.global(3, "wp_viewporter", 1)
[ 394932.738] wl_registry#2.global(4, "zxdg_output_manager_v1", 2)
[ 394932.744] wl_registry#2.global(5, "wp_presentation", 1)
[ 394932.750] wl_registry#2.global(6, "wp_single_pixel_buffer_manager_v1", 1)
[ 394932.759] wl_registry#2.global(7, "wp_tearing_control_manager_v1", 1)
[ 394932.764] wl_registry#2.global(8, "zwp_relative_pointer_manager_v1", 1)
[ 394932.775] wl_registry#2.global(9, "zwp_pointer_constraints_v1", 1)
[ 394932.780] wl_registry#2.global(10, "zwp_input_timestamps_manager_v1", 1)
[ 394932.789] wl_registry#2.global(11, "weston_capture_v1", 1)
[ 394932.795] wl_registry#2.global(12, "wl_data_device_manager", 3)
[ 394932.803] wl_registry#2.global(13, "wl_shm", 2)
[ 394932.809] wl_registry#2.global(14, "wl_drm", 2)
[ 394932.817] wl_registry#2.global(15, "zwp_linux_dmabuf_v1", 5)
[ 394932.829] wl_registry#2.global(16, "wl_seat", 7)
[ 394932.834] wl_registry#2.global(17, "wl_output", 4)
[ 394932.843] wl_registry#2.global(18, "zwp_input_panel_v1", 1)
[ 394932.849] wl_registry#2.global(19, "zwp_input_method_v1", 1)
[ 394932.857] wl_registry#2.global(20, "zwp_text_input_manager_v1", 1)
[ 394932.863] wl_registry#2.global(21, "xdg_wm_base", 5)
[ 394932.869] wl_registry#2.global(22, "weston_desktop_shell", 1)
[ 394932.875] wl_callback#3.done(226)
[ 394932.988] -> wl_registry#2.bind(1, "wl_compositor", 5, new id [unknown]#3)
[ 394933.005] -> wl_registry#2.bind(2, "wl_subcompositor", 1, new id [unknown]#4)
[ 394933.019] -> wl_registry#2.bind(17, "wl_output", 4, new id [unknown]#5)
[ 394933.030] -> wl_registry#2.bind(4, "zxdg_output_manager_v1", 2, new id [unknown]#6)
[ 394933.069] -> zxdg_output_manager_v1#6.get_xdg_output(new id zxdg_output_v1#7, wl_output#5)
[ 394933.102] -> wl_registry#2.bind(16, "wl_seat", 7, new id [unknown]#8)
[ 394933.166] -> wl_registry#2.bind(13, "wl_shm", 1, new id [unknown]#9)
[ 394933.215] -> wl_shm#9.create_pool(new id wl_shm_pool#10, fd 8, 2)
[ 394933.253] -> wl_registry#2.bind(21, "xdg_wm_base", 5, new id [unknown]#11)
[ 394933.278] -> wl_registry#2.bind(8, "zwp_relative_pointer_manager_v1", 1, new id [unknown]#12)
[ 394933.288] -> wl_registry#2.bind(9, "zwp_pointer_constraints_v1", 1, new id [unknown]#13)
[ 394933.302] -> wl_display#1.sync(new id wl_callback#14)
[ 394933.473] {Display Queue} wl_display#1.delete_id(14)
[ 394933.498] wl_output#5.geometry(0, 0, 1024, 640, 0, "wayland", "none", 0)
[ 394933.510] wl_output#5.scale(2)
[ 394933.516] wl_output#5.mode(3, 1836, 1544, 60000)
[ 394933.520] wl_output#5.name("wayland0")
[ 394933.528] wl_output#5.description("none")
[ 394933.534] wl_output#5.done()
[ 394933.539] zxdg_output_v1#7.logical_position(0, 0)
[ 394933.545] zxdg_output_v1#7.logical_size(918, 772)
[ 394933.552] zxdg_output_v1#7.name("wayland0")
[ 394933.558] zxdg_output_v1#7.done()
[ 394933.566] wl_seat#8.name("seat0")
[ 394933.571] wl_seat#8.capabilities(3)
[ 394933.578] wl_shm#9.format(0)
[ 394933.586] wl_shm#9.format(1)
[ 394933.591] wl_shm#9.format(875709016)
[ 394933.599] wl_shm#9.format(875708993)
[ 394933.606] wl_shm#9.format(875714642)
[ 394933.616] wl_shm#9.format(875708754)
[ 394933.620] wl_shm#9.format(875714626)
[ 394933.624] wl_shm#9.format(875708738)
[ 394933.664] wl_shm#9.format(875710290)
[ 394933.668] wl_shm#9.format(875710274)
[ 394933.672] wl_shm#9.format(909199186)
[ 394933.681] wl_shm#9.format(842093913)
[ 394933.685] wl_shm#9.format(875713881)
[ 394933.692] wl_shm#9.format(842094158)
[ 394933.696] wl_shm#9.format(909203022)
[ 394933.703] wl_shm#9.format(875714126)
[ 394933.708] wl_shm#9.format(1448695129)
[ 394933.715] wl_shm#9.format(1448434008)
[ 394933.720] wl_shm#9.format(875708993)
[ 394933.731] wl_shm#9.format(808665665)
[ 394933.744] wl_shm#9.format(808665688)
[ 394933.750] wl_shm#9.format(1211384385)
[ 394933.755] wl_shm#9.format(1211384408)
[ 394933.759] wl_shm#9.format(942948929)
[ 394933.763] wl_shm#9.format(942948952)
[ 394933.771] wl_callback#14.done(226)
[ 394933.815] -> wl_seat#8.get_keyboard(new id wl_keyboard#14)
[ 394935.386] -> wl_compositor#3.create_surface(new id wl_surface#15)
[ 394935.406] -> wl_seat#8.get_pointer(new id wl_pointer#16)
[ 394935.425] -> zwp_relative_pointer_manager_v1#12.get_relative_pointer(new id zwp_relative_pointer_v1#17, wl_pointer#16)
[ 394935.530] -> wl_compositor#3.create_surface(new id wl_surface#18)
[ 394935.550] -> xdg_wm_base#11.get_xdg_surface(new id xdg_surface#19, wl_surface#18)
[ 394935.563] -> xdg_surface#19.get_toplevel(new id xdg_toplevel#20)
[ 394935.579] -> wl_compositor#3.create_region(new id wl_region#21)
[ 394935.588] -> wl_region#21.add(0, 0, 2147483647, 2147483647)
[ 394935.599] -> wl_surface#18.set_opaque_region(wl_region#21)
[ 394935.605] -> wl_region#21.destroy()
[ 394935.620] -> xdg_toplevel#20.set_title("bug")
[ 394935.634] -> xdg_toplevel#20.set_min_size(2, 1)
[ 394935.641] -> xdg_toplevel#20.set_max_size(0, 0)
[ 394935.646] -> xdg_toplevel#20.set_min_size(400, 400)
[ 394935.655] -> xdg_toplevel#20.set_max_size(400, 400)
[ 394935.663] -> wl_surface#18.commit()
[ 394935.675] -> wl_display#1.sync(new id wl_callback#22)
[ 394935.826] {Display Queue} wl_display#1.delete_id(21)
[ 394935.841] {Display Queue} wl_display#1.delete_id(22)
[ 394935.846] wl_keyboard#14.repeat_info(25, 600)
[ 394935.861] wl_keyboard#14.keymap(1, fd 10, 72284)
[ 394935.869] xdg_toplevel#20.wm_capabilities(array[12])
[ 394935.876] wl_callback#22.done(226)
[ 394935.882] xdg_toplevel#20.configure(0, 0, array[0])
[ 394935.887] xdg_surface#19.configure(227)
[ 394936.990] -> xdg_surface#19.ack_configure(227)
[ 394941.778] -> wl_shm#9.create_pool(new id wl_shm_pool#22, fd 12, 1)
[ 394941.860] -> wl_compositor#3.create_surface(new id wl_surface#21)
[ 394941.909] -> wl_subcompositor#4.get_subsurface(new id wl_subsurface#23, wl_surface#21, wl_surface#18)
[ 394941.938] -> wl_subsurface#23.set_sync()
[ 394941.959] -> wl_compositor#3.create_surface(new id wl_surface#24)
[ 394941.983] -> wl_subcompositor#4.get_subsurface(new id wl_subsurface#25, wl_surface#24, wl_surface#18)
[ 394942.006] -> wl_subsurface#25.set_sync()
[ 394942.027] -> wl_compositor#3.create_surface(new id wl_surface#26)
[ 394942.051] -> wl_subcompositor#4.get_subsurface(new id wl_subsurface#27, wl_surface#26, wl_surface#18)
[ 394942.076] -> wl_subsurface#27.set_sync()
[ 394942.097] -> wl_compositor#3.create_surface(new id wl_surface#28)
[ 394942.123] -> wl_subcompositor#4.get_subsurface(new id wl_subsurface#29, wl_surface#28, wl_surface#18)
[ 394942.143] -> wl_subsurface#29.set_sync()
[ 394942.162] -> wl_compositor#3.create_surface(new id wl_surface#30)
[ 394942.211] -> wl_subcompositor#4.get_subsurface(new id wl_subsurface#31, wl_surface#30, wl_surface#18)
[ 394942.231] -> wl_subsurface#31.set_sync()
[ 394962.925] -> wl_compositor#3.create_region(new id wl_region#32)
[ 394962.963] -> wl_region#32.add(0, 0, 2147483647, 2147483647)
[ 394962.973] -> wl_surface#18.set_opaque_region(wl_region#32)
[ 394962.980] -> wl_region#32.destroy()
[ 394962.993] -> xdg_surface#19.set_window_geometry(0, -35, 400, 435)
[examples/bug.rs:51:17] size = PhysicalSize {
width: 400,
height: 400,
}
[ 394963.134] -> wl_shm_pool#22.resize(85888)
[ 394963.164] -> wl_shm_pool#22.create_buffer(new id wl_buffer#33, 0, 488, 44, 1952, 0)
[ 394964.794] -> wl_subsurface#23.set_sync()
[ 394964.804] -> wl_surface#21.set_buffer_scale(1)
[ 394964.811] -> wl_subsurface#23.set_position(-44, -79)
[ 394964.821] -> wl_surface#21.attach(wl_buffer#33, 0, 0)
[ 394964.828] -> wl_surface#21.damage_buffer(0, 0, 2147483647, 2147483647)
[ 394964.836] -> wl_compositor#3.create_region(new id wl_region#34)
[ 394964.844] -> wl_region#34.add(32, 32, 424, 12)
[ 394964.850] -> wl_surface#21.set_input_region(wl_region#34)
[ 394964.862] -> wl_region#34.destroy()
[ 394964.878] -> wl_surface#21.commit()
[ 394964.887] -> wl_shm_pool#22.resize(171776)
[ 394964.908] -> wl_shm_pool#22.create_buffer(new id wl_buffer#35, 85888, 44, 435, 176, 0)
[ 394965.943] -> wl_subsurface#25.set_sync()
[ 394965.952] -> wl_surface#24.set_buffer_scale(1)
[ 394965.958] -> wl_subsurface#25.set_position(-44, -35)
[ 394965.967] -> wl_surface#24.attach(wl_buffer#35, 0, 0)
[ 394965.973] -> wl_surface#24.damage_buffer(0, 0, 2147483647, 2147483647)
[ 394965.979] -> wl_compositor#3.create_region(new id wl_region#36)
[ 394965.987] -> wl_region#36.add(32, 0, 12, 435)
[ 394965.993] -> wl_surface#24.set_input_region(wl_region#36)
[ 394965.999] -> wl_region#36.destroy()
[ 394966.011] -> wl_surface#24.commit()
[ 394966.018] -> wl_shm_pool#22.resize(343552)
[ 394966.040] -> wl_shm_pool#22.create_buffer(new id wl_buffer#37, 162496, 44, 435, 176, 0)
[ 394966.987] -> wl_subsurface#27.set_sync()
[ 394966.995] -> wl_surface#26.set_buffer_scale(1)
[ 394967.000] -> wl_subsurface#27.set_position(400, -35)
[ 394967.006] -> wl_surface#26.attach(wl_buffer#37, 0, 0)
[ 394967.012] -> wl_surface#26.damage_buffer(0, 0, 2147483647, 2147483647)
[ 394967.018] -> wl_compositor#3.create_region(new id wl_region#38)
[ 394967.025] -> wl_region#38.add(0, 0, 12, 435)
[ 394967.031] -> wl_surface#26.set_input_region(wl_region#38)
[ 394967.036] -> wl_region#38.destroy()
[ 394967.041] -> wl_surface#26.commit()
[ 394967.049] -> wl_shm_pool#22.create_buffer(new id wl_buffer#39, 239104, 488, 44, 1952, 0)
[ 394967.649] -> wl_subsurface#29.set_sync()
[ 394967.657] -> wl_surface#28.set_buffer_scale(1)
[ 394967.663] -> wl_subsurface#29.set_position(-44, 400)
[ 394967.668] -> wl_surface#28.attach(wl_buffer#39, 0, 0)
[ 394967.674] -> wl_surface#28.damage_buffer(0, 0, 2147483647, 2147483647)
[ 394967.680] -> wl_compositor#3.create_region(new id wl_region#40)
[ 394967.686] -> wl_region#40.add(32, 0, 424, 12)
[ 394967.692] -> wl_surface#28.set_input_region(wl_region#40)
[ 394967.697] -> wl_region#40.destroy()
[ 394967.702] -> wl_surface#28.commit()
[ 394967.709] -> wl_shm_pool#22.resize(687104)
[ 394967.737] -> wl_shm_pool#22.create_buffer(new id wl_buffer#41, 324992, 402, 35, 1608, 0)
[ 394968.585] -> wl_subsurface#31.set_sync()
[ 394968.599] -> wl_surface#30.set_buffer_scale(1)
[ 394968.605] -> wl_subsurface#31.set_position(-1, -35)
[ 394968.612] -> wl_surface#30.attach(wl_buffer#41, 0, 0)
[ 394968.618] -> wl_surface#30.damage_buffer(0, 0, 2147483647, 2147483647)
[ 394968.623] -> wl_surface#30.commit()
[ 394968.648] -> wl_surface#18.frame(new id wl_callback#42)
[ 394968.681] -> wl_display#1.get_registry(new id wl_registry#43)
[ 394968.691] -> wl_display#1.sync(new id wl_callback#44)
[ 394968.966] {Display Queue} wl_display#1.delete_id(32)
[ 394968.978] {Display Queue} wl_display#1.delete_id(34)
[ 394968.983] {Display Queue} wl_display#1.delete_id(36)
[ 394968.987] {Display Queue} wl_display#1.delete_id(38)
[ 394968.990] {Display Queue} wl_display#1.delete_id(40)
[ 394968.994] {Display Queue} wl_display#1.delete_id(44)
[ 394968.997] wl_registry#43.global(1, "wl_compositor", 5)
[ 394969.017] wl_registry#43.global(2, "wl_subcompositor", 1)
[ 394969.023] wl_registry#43.global(3, "wp_viewporter", 1)
[ 394969.035] wl_registry#43.global(4, "zxdg_output_manager_v1", 2)
[ 394969.041] wl_registry#43.global(5, "wp_presentation", 1)
[ 394969.050] wl_registry#43.global(6, "wp_single_pixel_buffer_manager_v1", 1)
[ 394969.056] wl_registry#43.global(7, "wp_tearing_control_manager_v1", 1)
[ 394969.064] wl_registry#43.global(8, "zwp_relative_pointer_manager_v1", 1)
[ 394969.069] wl_registry#43.global(9, "zwp_pointer_constraints_v1", 1)
[ 394969.079] wl_registry#43.global(10, "zwp_input_timestamps_manager_v1", 1)
[ 394969.085] wl_registry#43.global(11, "weston_capture_v1", 1)
[ 394969.090] wl_registry#43.global(12, "wl_data_device_manager", 3)
[ 394969.103] wl_registry#43.global(13, "wl_shm", 2)
[ 394969.109] wl_registry#43.global(14, "wl_drm", 2)
[ 394969.117] wl_registry#43.global(15, "zwp_linux_dmabuf_v1", 5)
[ 394969.131] wl_registry#43.global(16, "wl_seat", 7)
[ 394969.143] wl_registry#43.global(17, "wl_output", 4)
[ 394969.149] wl_registry#43.global(18, "zwp_input_panel_v1", 1)
[ 394969.157] wl_registry#43.global(19, "zwp_input_method_v1", 1)
[ 394969.162] wl_registry#43.global(20, "zwp_text_input_manager_v1", 1)
[ 394969.170] wl_registry#43.global(21, "xdg_wm_base", 5)
[ 394969.176] wl_registry#43.global(22, "weston_desktop_shell", 1)
[ 394969.184] wl_callback#44.done(227)
[ 394969.209] -> wl_registry#43.bind(13, "wl_shm", 1, new id [unknown]#44)
[ 394969.278] -> wl_shm#44.create_pool(new id wl_shm_pool#40, fd 13, 1048576)
[ 394969.301] -> wl_shm_pool#40.create_buffer(new id wl_buffer#38, 0, 400, 400, 1600, 1)
[ 394969.320] -> wl_shm#44.create_pool(new id wl_shm_pool#36, fd 15, 1048576)
[ 394969.330] -> wl_shm_pool#36.create_buffer(new id wl_buffer#34, 0, 400, 400, 1600, 1)
[ 394970.202] -> wl_surface#18.attach(wl_buffer#34, 0, 0)
[ 394970.212] -> wl_surface#18.damage_buffer(0, 0, 400, 400)
[ 394970.218] -> wl_surface#18.commit()
[ 394970.326] wl_surface#18.enter(wl_output#5)
[ 394970.340] wl_surface#30.enter(wl_output#5)
[ 394970.345] wl_surface#28.enter(wl_output#5)
[ 394970.350] wl_surface#26.enter(wl_output#5)
[ 394970.355] wl_surface#24.enter(wl_output#5)
[ 394970.361] wl_surface#21.enter(wl_output#5)
[ 394970.366] wl_keyboard#14.enter(229, wl_surface#18, array[0])
[ 394970.377] wl_keyboard#14.modifiers(229, 0, 0, 0, 0)
[ 394970.383] xdg_toplevel#20.configure(0, 0, array[4])
[ 394970.391] xdg_surface#19.configure(231)
[ 394970.454] -> wl_surface#18.set_buffer_scale(2)
[ 394970.542] -> xdg_surface#19.ack_configure(231)
[examples/bug.rs:51:17] size = PhysicalSize {
width: 800,
height: 800,
}
[ 394970.647] -> wl_shm_pool#22.resize(1374208)
[ 394970.686] -> wl_shm_pool#22.create_buffer(new id wl_buffer#32, 381312, 976, 88, 3904, 0)
[ 394976.939] -> wl_subsurface#23.set_sync()
[ 394976.964] -> wl_surface#21.set_buffer_scale(2)
[ 394976.970] -> wl_subsurface#23.set_position(-44, -79)
[ 394976.981] -> wl_surface#21.attach(wl_buffer#32, 0, 0)
[ 394976.989] -> wl_surface#21.damage_buffer(0, 0, 2147483647, 2147483647)
[ 394977.000] -> wl_compositor#3.create_region(new id wl_region#45)
[ 394977.012] -> wl_region#45.add(32, 32, 424, 12)
[ 394977.020] -> wl_surface#21.set_input_region(wl_region#45)
[ 394977.026] -> wl_region#45.destroy()
[ 394977.036] -> wl_surface#21.commit()
[ 394977.053] -> wl_shm_pool#22.create_buffer(new id wl_buffer#46, 724864, 88, 870, 352, 0)
[ 394978.915] -> wl_subsurface#25.set_sync()
[ 394978.925] -> wl_surface#24.set_buffer_scale(2)
[ 394978.931] -> wl_subsurface#25.set_position(-44, -35)
[ 394978.938] -> wl_surface#24.attach(wl_buffer#46, 0, 0)
[ 394978.944] -> wl_surface#24.damage_buffer(0, 0, 2147483647, 2147483647)
[ 394978.952] -> wl_compositor#3.create_region(new id wl_region#47)
[ 394978.959] -> wl_region#47.add(32, 0, 12, 435)
[ 394978.966] -> wl_surface#24.set_input_region(wl_region#47)
[ 394978.971] -> wl_region#47.destroy()
[ 394978.983] -> wl_surface#24.commit()
[ 394978.991] -> wl_shm_pool#22.create_buffer(new id wl_buffer#48, 1031104, 88, 870, 352, 0)
[ 394982.320] -> wl_subsurface#27.set_sync()
[ 394982.347] -> wl_surface#26.set_buffer_scale(2)
[ 394982.354] -> wl_subsurface#27.set_position(400, -35)
[ 394982.362] -> wl_surface#26.attach(wl_buffer#48, 0, 0)
[ 394982.369] -> wl_surface#26.damage_buffer(0, 0, 2147483647, 2147483647)
[ 394982.378] -> wl_compositor#3.create_region(new id wl_region#49)
[ 394982.389] -> wl_region#49.add(0, 0, 12, 435)
[ 394982.395] -> wl_surface#26.set_input_region(wl_region#49)
[ 394982.407] -> wl_region#49.destroy()
[ 394982.414] -> wl_surface#26.commit()
[ 394982.432] -> wl_shm_pool#22.resize(2748416)
[ 394982.554] -> wl_shm_pool#22.create_buffer(new id wl_buffer#50, 1337344, 976, 88, 3904, 0)
[ 394985.244] -> wl_subsurface#29.set_sync()
[ 394985.264] -> wl_surface#28.set_buffer_scale(2)
[ 394985.270] -> wl_subsurface#29.set_position(-44, 400)
[ 394985.277] -> wl_surface#28.attach(wl_buffer#50, 0, 0)
[ 394985.291] -> wl_surface#28.damage_buffer(0, 0, 2147483647, 2147483647)
[ 394985.319] -> wl_compositor#3.create_region(new id wl_region#51)
[ 394985.329] -> wl_region#51.add(32, 0, 424, 12)
[ 394985.336] -> wl_surface#28.set_input_region(wl_region#51)
[ 394985.341] -> wl_region#51.destroy()
[ 394985.348] -> wl_surface#28.commit()
[ 394985.358] -> wl_shm_pool#22.create_buffer(new id wl_buffer#52, 1680896, 804, 70, 3216, 0)
[ 394987.810] -> wl_subsurface#31.set_sync()
[ 394987.825] -> wl_surface#30.set_buffer_scale(2)
[ 394987.832] -> wl_subsurface#31.set_position(-1, -35)
[ 394987.839] -> wl_surface#30.attach(wl_buffer#52, 0, 0)
[ 394987.845] -> wl_surface#30.damage_buffer(0, 0, 2147483647, 2147483647)
[ 394987.850] -> wl_surface#30.commit()
[ 394987.970] {Display Queue} wl_display#1.delete_id(42)
[ 394987.977] wl_buffer#41.release()
[ 394987.990] -> wl_buffer#41.destroy()
[ 394987.999] wl_buffer#39.release()
[ 394988.005] -> wl_buffer#39.destroy()
[ 394988.017] wl_buffer#37.release()
[ 394988.025] -> wl_buffer#37.destroy()
[ 394988.034] wl_buffer#35.release()
[ 394988.041] -> wl_buffer#35.destroy()
[ 394988.047] wl_buffer#33.release()
[ 394988.052] -> wl_buffer#33.destroy()
[ 394988.062] wl_callback#42.done(395902584)
[ 394988.151] -> wl_surface#18.frame(new id wl_callback#42)
[ 394988.181] -> wl_buffer#38.destroy()
[ 394988.209] -> wl_shm_pool#40.resize(4194304)
[ 394988.232] -> wl_shm_pool#40.create_buffer(new id wl_buffer#53, 0, 800, 800, 3200, 1)
[ 394991.667] wl_shm#44.format(0)
[ 394991.686] wl_shm#44.format(1)
[ 394991.691] wl_shm#44.format(875709016)
[ 394991.695] wl_shm#44.format(875708993)
[ 394991.700] wl_shm#44.format(875714642)
[ 394991.705] wl_shm#44.format(875708754)
[ 394991.710] wl_shm#44.format(875714626)
[ 394991.714] wl_shm#44.format(875708738)
[ 394991.718] wl_shm#44.format(875710290)
[ 394991.723] wl_shm#44.format(875710274)
[ 394991.727] wl_shm#44.format(909199186)
[ 394991.738] wl_shm#44.format(842093913)
[ 394991.743] wl_shm#44.format(875713881)
[ 394991.747] wl_shm#44.format(842094158)
[ 394991.755] wl_shm#44.format(909203022)
[ 394991.759] wl_shm#44.format(875714126)
[ 394991.768] wl_shm#44.format(1448695129)
[ 394991.774] wl_shm#44.format(1448434008)
[ 394991.784] wl_shm#44.format(875708993)
[ 394991.788] wl_shm#44.format(808665665)
[ 394991.796] wl_shm#44.format(808665688)
[ 394991.801] wl_shm#44.format(1211384385)
[ 394991.805] wl_shm#44.format(1211384408)
[ 394991.813] wl_shm#44.format(942948929)
[ 394991.818] wl_shm#44.format(942948952)
[ 394991.825] wl_buffer#34.release()
[ 394991.859] -> wl_surface#18.attach(wl_buffer#53, 0, 0)
[ 394991.868] -> wl_surface#18.damage_buffer(0, 0, 800, 800)
[ 394991.873] -> wl_surface#18.commit()
[ 394996.321] {Display Queue} wl_display#1.delete_id(45)
[ 394996.507] {Display Queue} wl_display#1.delete_id(47)
[ 394996.526] {Display Queue} wl_display#1.delete_id(49)
[ 394996.536] {Display Queue} wl_display#1.delete_id(51)
[ 394996.545] {Display Queue} wl_display#1.delete_id(41)
[ 394996.554] {Display Queue} wl_display#1.delete_id(39)
[ 394996.563] {Display Queue} wl_display#1.delete_id(37)
[ 394996.572] {Display Queue} wl_display#1.delete_id(35)
[ 394996.582] {Display Queue} wl_display#1.delete_id(33)
[ 394996.592] {Display Queue} wl_display#1.delete_id(38)
[ 394996.601] {Display Queue} wl_display#1.delete_id(42)
[ 394996.610] wl_buffer#52.release()
[ 394996.794] -> wl_buffer#52.destroy()
[ 394996.824] wl_buffer#50.release()
[ 394996.840] -> wl_buffer#50.destroy()
[ 394996.854] wl_buffer#48.release()
[ 394996.878] -> wl_buffer#48.destroy()
[ 394996.892] wl_buffer#46.release()
[ 394996.905] -> wl_buffer#46.destroy()
[ 394996.920] wl_buffer#32.release()
[ 394996.939] -> wl_buffer#32.destroy()
[ 394996.957] wl_callback#42.done(395902598)
Weston never emits non-0x0 configures:
$ rg 'xdg_toplevel.*configure' weston-trace
102:[ 394935.882] xdg_toplevel#20.configure(0, 0, array[0])
233:[ 394970.383] xdg_toplevel#20.configure(0, 0, array[4])
Despite that, we still get a 800x800 window.
Yeah, that's because we preserve size you've passed only for the first configure, but weston delivers scale later on, so we end up with what we end up with.
~ wl-paste | rg 'configure|scale|preferred|Physical|width:|height:'
[ 394933.510] wl_output#5.scale(2)
[ 394935.882] xdg_toplevel#20.configure(0, 0, array[0])
[ 394935.887] xdg_surface#19.configure(227)
[ 394936.990] -> xdg_surface#19.ack_configure(227)
[examples/bug.rs:51:17] size = PhysicalSize {
width: 400,
height: 400,
[ 394964.804] -> wl_surface#21.set_buffer_scale(1)
[ 394965.952] -> wl_surface#24.set_buffer_scale(1)
[ 394966.995] -> wl_surface#26.set_buffer_scale(1)
[ 394967.657] -> wl_surface#28.set_buffer_scale(1)
[ 394968.599] -> wl_surface#30.set_buffer_scale(1)
[ 394970.383] xdg_toplevel#20.configure(0, 0, array[4])
[ 394970.391] xdg_surface#19.configure(231)
[ 394970.454] -> wl_surface#18.set_buffer_scale(2)
[ 394970.542] -> xdg_surface#19.ack_configure(231)
[examples/bug.rs:51:17] size = PhysicalSize {
width: 800,
height: 800,
[ 394976.964] -> wl_surface#21.set_buffer_scale(2)
[ 394978.925] -> wl_surface#24.set_buffer_scale(2)
[ 394982.347] -> wl_surface#26.set_buffer_scale(2)
[ 394985.264] -> wl_surface#28.set_buffer_scale(2)
[ 394987.825] -> wl_surface#30.set_buffer_scale(2)
Like the logic only applies to correct the size for the initial configure, and after that it'll be following wayland model of doing things. So if you want to adjust to scale change event in a way to preserve physical size you should adjust it yourself (your window will shrink which is undesired behavior).
But this thing will change in winit, so you'd explicitly know when things like that happen. The original issue is that min/max sizes on the wire are logical.
The docs do mention that you can get resized if you've used resizable(false) due to DPI changes and generally DPI change could resize you and that's what happens. You can adjust back right away when handling ScaleFactorChanged event, it has a surface_size_writer to resize back if you really want to preserve physical size and shrink/enlarge your window due to scale change.
In general we have the following situations:
- Sent size must be obeyed.
- Sent size should be obeyed.
- User must pick size.
And we can change between those states resulting in issues to what we should restore to and when.
And if e.g. resizable(false) is involved with a physical size, where everything on Wayland is logical, we start having interesting set of issues and combination of solutions(which for now assume that your window is not resized from the user of your application perspective, e.i. app just becomes crisper for the user but doesn't change the amount of space on the output scape it occupies).
We can try to preserve the user preference as long as we can, but until what point? Like on Wayland when scale changes, your buffer (physical size) adjusts, so that's why you had the 800x800 resize. The resizable docs mention that exact case (which is what happens) for you, and gives an option to deal with that in the SurfaceScaleChanged event on its own.
But API wise it indeed results in a behavior that while expected from the Wayland client side of view, not expected from what you've asked winit to do, so instead of having a window of fixed buffer size, you have a window of fixed logical size on the screen.
What's your use case here btw? Show pixel perfect image, I'd guess?
I've reopened issue, but our docs do mention that exact issue that you can still get resized the result of that is scale change, so technically there's no issue, but because scale on Wayland is always until we get the initial configure, but we get initial configure only after setting min/max constraints we have an issue like that.
You can adjust back right away when handling
ScaleFactorChangedevent, it has asurface_size_writerto resize back if you really want to preserve physical size and shrink/enlarge your window due to scale change.
InnerSizeWriter does not adjust min/max sizes, so doing that does not seem to be enough: it results in min/max size disagreeing with requested inner size, and at least in sway compositor will resize the window back to min size on the next event. I’ve also tried doing
self.window.as_ref().unwrap().set_min_inner_size(Some(PhysicalSize::new(400, 400)));
self.window.as_ref().unwrap().set_max_inner_size(Some(PhysicalSize::new(400, 400)));
inner_size_writer.request_inner_size(PhysicalSize::new(400, 400)).unwrap();
which still doesn’t work on sway, but I haven’t figured out why yet.
What's your use case here btw? Show pixel perfect image, I'd guess?
I’m debugging an issue with egui/eframe. eframe always sets sizes using physical pixels (with a guessed scale factor), so creating a non-resizeable window always leads to a broken window state. I am convinced that eframe is wrong here and should not try to convert to physical pixels (and I’ll submit a patch there), but setting window size in physical pixels and always having it immediately changed on a scaled output also feels wrong. I’ll try it on some more compositors to check whether physical inner size with .set_resizeable(false) works there.
InnerSizeWriter does not adjust min/max sizes, s
Yeah, that's true and I can see that compositor may want to resize you back for whatever reason. Though, the issue is likely that non-resizable thing, since min/max sizes start to interfere.
self.window.as_ref().unwrap().set_min_inner_size(Some(PhysicalSize::new(400, 400))); self.window.as_ref().unwrap().set_max_inner_size(Some(PhysicalSize::new(400, 400))); inner_size_writer.request_inner_size(PhysicalSize::new(400, 400)).unwrap();
sway doesn't like any manipulations like that, and keep in mind that you must draw after changing sizes, since the state is double buffered. I can suggest to check with gnome/niri/kde since those are generally saner when it comes to min/max sizes. GNOME/KDE in particular since they use stacking window management.
I’m debugging an issue with egui/eframe. eframe always sets sizes using physical pixels (with a guessed scale factor), so creating a non-resizeable window always leads to a broken window state. I am convinced that eframe is wrong here and should not try to convert to physical pixels (and I’ll submit a patch there), but setting window size in physical pixels and always having it immediately changed on a scaled output also feels wrong. I’ll try it on some more compositors to check whether physical inner size with .set_resizeable(false) works there.
Hm, we can try to preserve physical size as much as we can within winit when non-resizable is used, but then we'd need to readjust docs, since right now we state that DPI change can resize you if you have non-resizable window, which is what happening.
We also can not assume scale other than 1 for the min/max sizes since that what docs say.
For cases without non-resizable I generally would rather change the API we have in winit to have sizes more synced with the user, which is planned for 0.31, so in case user has to pick the size they can do so and pass size straight back to winit.
right now we state that DPI change can resize you if you have non-resizable window, which is what happening
Yeah, I guess the non-intuitive thing is that DPI change happens immediately, since it’s always assumed 1.0 on start. I also do not see a good way out of this without an API change and my actual usecase doesn’t require stable physical sizes anyway. Maybe the best thing to do here is to just document that trying to use physical sizes with non-resizeable windows is almost certainly wrong, because there’s no good way to adjust it to DPI change and DPI change will happen even if you have a single display with fixed scale factor.
keep in mind that you must draw after changing sizes
I added .request_redraw(), but it didn’t change the behaviour. My gut feeling is that sway is being weird here, since I see the correct min/max size updates in the trace.
Yeah, I guess the non-intuitive thing is that DPI change happens immediately, since it’s always assumed 1.0 on start.
Yes, but generally, if it arrives along the first configure we'd preserve the physical size. IIRC you can test that on niri, it should work there. IIRC gnome as well. Weston doesn't support new scale protocols, so scale is unknown until the window is drown, and drawing is done always with scale 1 in such cases. sway has bugs with initial sizes and always sends a bunch of garbage along the initial configure.
I also do not see a good way out of this without an API change and my actual usecase doesn’t require stable physical sizes anyway. Maybe the best thing to do here is to just document that trying to use physical sizes with non-resizeable windows is almost certainly wrong, because there’s no good way to adjust it to DPI change and DPI change will happen even if you have a single display with fixed scale factor.
I also think so. From the user of the app perspective logical is the real non-resizable, since window content layout doesn't change and window occupies the same logical amount of pixels. Though, if you want to show an image I can assume that you'd want to use physical size instead. Issue is purely that winit accepts both logical/physical, but wayland is all the way logical. Keeping physical size when DPI changes is not expected, since the window will shrink/enlarge, since you try to use the same buffer, for more physical pixels.
I added .request_redraw(), but it didn’t change the behaviour. My gut feeling is that sway is being weird here, since I see the correct min/max size updates in the trace.
yeah, that what I remember myself when testing on sway in the past, it just doesn't like any of those size manipulations.
Yeah, I initially expected that winit will remember size as I specified it, without converting to logical, and then react to scale changes by converting it to logical and using that to update window size if required. I still think this is more intuitive, but that wouldn’t actually help my case if sway can’t do runtime min/max size update.
but that wouldn’t actually help my case if sway can’t do runtime min/max size update.
I mean it should, but... It's true though that if we try to adjust things they likely won't go through on sway and bug won't go away due to sway bug and because of the min/max sizes being logical on Wayland and scale being 1 the min max size will stuck on sway to what we've passed initially.
Though, IIRC toggling tiling state on sway will re-apply those, but it's pretty much irrelevant since you'd have bugged states in-between.
Storing size and min/max size as specified and automatically adjusting logical size on scale factor updates as needed does seem like a more intuitive behaviour to me in general. For a compositors that properly handle it this probably comes the closest to following the developers intent in setting a fixed physical size.
The point is more like, do you want your window to always have the same buffer size or your window to always be of the same size for the user? I guess that sort of preference is what Logical/Physical is, more or less. Like we can adjust, but we should be careful with those things to avoid configure loops.
My intuition here is that if you specify your window size in physical pixels, you probably care about the buffer size and not the visual size — otherwise you would’ve probably used logical pixels. This is not the case in my original egui issue though, egui actually wants the same apparent size.
This is not the case in my original egui issue though, egui actually wants the same apparent size.
Then they want to use logical sizes all the way and this issue will go away.
Then they want to use logical sizes all the way and this issue will go away.
Yeah, I already wrote a patch to use logical sizes there; using physical sizes when you want a logical size is definitely wrong.