From f777b4c52187d734e3002ee8dd9d1bc031403196 Mon Sep 17 00:00:00 2001 From: atagen Date: Sun, 31 May 2026 18:11:58 +1000 Subject: [PATCH 1/2] it: fix integration geometry and tab scrolling --- src/ifs/wl_surface.rs | 19 ++++--- src/it/test_ifs/test_viewport.rs | 20 +++++++ src/it/tests/t0002_window.rs | 23 +++----- src/it/tests/t0003_multi_window.rs | 21 ++++++-- .../tests/t0007_subsurface/screenshot_1.qoi | Bin 8141 -> 8134 bytes .../tests/t0007_subsurface/screenshot_2.qoi | Bin 7832 -> 7825 bytes src/it/tests/t0014_container_scroll_focus.rs | 19 ++++--- src/it/tests/t0015_scroll_partial.rs | 18 ++++--- .../t0020_surface_offset/screenshot_1.qoi | Bin 8141 -> 8138 bytes .../t0020_surface_offset/screenshot_2.qoi | Bin 8141 -> 8138 bytes .../t0023_xdg_activation/screenshot_1.qoi | Bin 7925 -> 7849 bytes .../t0026_output_transform/screenshot_1.qoi | Bin 7835 -> 7832 bytes .../screenshot_1.qoi | Bin 11228 -> 11083 bytes .../screenshot_2.qoi | Bin 11228 -> 11083 bytes .../t0029_double_click_float/screenshot_1.qoi | Bin 7834 -> 9988 bytes .../t0029_double_click_float/screenshot_2.qoi | Bin 10118 -> 9988 bytes .../t0037_toplevel_drag/screenshot_2.qoi | Bin 7834 -> 7831 bytes .../screenshot_1.qoi | Bin 8139 -> 8136 bytes .../screenshot_2.qoi | Bin 8143 -> 8140 bytes .../t0039_alpha_modifier/screenshot_1.qoi | Bin 8142 -> 8140 bytes .../t0039_alpha_modifier/screenshot_2.qoi | Bin 8145 -> 8143 bytes .../tests/t0041_input_method/screenshot_1.qoi | Bin 7831 -> 7828 bytes .../tests/t0041_input_method/screenshot_2.qoi | Bin 8141 -> 8138 bytes .../tests/t0041_input_method/screenshot_3.qoi | Bin 7831 -> 7828 bytes .../t0042_toplevel_select/screenshot_1.qoi | Bin 10802 -> 10799 bytes .../t0042_toplevel_select/screenshot_2.qoi | Bin 10800 -> 10799 bytes .../t0042_toplevel_select/screenshot_3.qoi | Bin 10800 -> 10799 bytes .../t0042_toplevel_select/screenshot_4.qoi | Bin 9671 -> 9634 bytes src/it/tests/t0047_surface_damage.rs | 24 +++++---- src/tree/container.rs | 51 ++++++++++++------ 30 files changed, 123 insertions(+), 72 deletions(-) diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 547b7e2a..4224e727 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -1520,25 +1520,25 @@ impl WlSurface { let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds()); let pos = self.buffer_abs_pos.get(); let apply_damage = |pos: Rect| { - if pending.damage_full { - let mut damage = pos; + let clip_damage = |mut damage: Rect| { + damage = damage.intersect(pos); if let Some(bounds) = bounds { damage = damage.intersect(bounds); } - self.client.state.damage(damage); + damage + }; + if pending.damage_full { + self.client.state.damage(clip_damage(pos)); } else { let matrix = self.damage_matrix.get(); if let Some(buffer) = self.buffer.get() { for damage in &pending.buffer_damage { - let mut damage = matrix.apply( + let damage = matrix.apply( pos.x1(), pos.y1(), damage.intersect(buffer.buffer.buf.rect), ); - if let Some(bounds) = bounds { - damage = damage.intersect(bounds); - } - self.client.state.damage(damage); + self.client.state.damage(clip_damage(damage)); } } for damage in &pending.surface_damage { @@ -1550,8 +1550,7 @@ impl WlSurface { let y2 = (damage.y2() + scale - 1) / scale; damage = Rect::new_saturating(x1, y1, x2, y2); } - damage = damage.intersect(bounds.unwrap_or(pos)); - self.client.state.damage(damage); + self.client.state.damage(clip_damage(damage)); } } }; diff --git a/src/it/test_ifs/test_viewport.rs b/src/it/test_ifs/test_viewport.rs index b25105c8..e08266de 100644 --- a/src/it/test_ifs/test_viewport.rs +++ b/src/it/test_ifs/test_viewport.rs @@ -29,6 +29,17 @@ impl TestViewport { Ok(()) } + pub fn unset_source(&self) -> Result<(), TestError> { + self.tran.send(SetSource { + self_id: self.id, + x: Fixed::from_int(-1), + y: Fixed::from_int(-1), + width: Fixed::from_int(-1), + height: Fixed::from_int(-1), + })?; + Ok(()) + } + pub fn set_destination(&self, width: i32, height: i32) -> Result<(), TestError> { self.tran.send(SetDestination { self_id: self.id, @@ -37,6 +48,15 @@ impl TestViewport { })?; Ok(()) } + + pub fn unset_destination(&self) -> Result<(), TestError> { + self.tran.send(SetDestination { + self_id: self.id, + width: -1, + height: -1, + })?; + Ok(()) + } } impl Drop for TestViewport { diff --git a/src/it/tests/t0002_window.rs b/src/it/tests/t0002_window.rs index 84571c57..28ee359f 100644 --- a/src/it/tests/t0002_window.rs +++ b/src/it/tests/t0002_window.rs @@ -1,7 +1,6 @@ use { crate::{ it::{test_error::TestError, testrun::TestRun}, - rect::Rect, tree::Node, }, std::rc::Rc, @@ -11,29 +10,19 @@ testcase!(); /// Create and map a single surface async fn test(run: Rc) -> Result<(), TestError> { - run.backend.install_default()?; + let ds = run.create_default_setup().await?; let client = run.create_client().await?; let window = client.create_window().await?; window.map().await?; - tassert_eq!(window.tl.core.width.get(), 800); - tassert_eq!( - window.tl.core.height.get(), - 600 - 2 * run.state.theme.title_plus_underline_height() - ); + let workspace_rect = ds.output.workspace_rect.get(); - tassert_eq!( - window.tl.server.node_absolute_position(), - Rect::new_sized( - 0, - 2 * run.state.theme.title_plus_underline_height(), - window.tl.core.width.get(), - window.tl.core.height.get(), - ) - .unwrap() - ); + tassert_eq!(window.tl.core.width.get(), workspace_rect.width()); + tassert_eq!(window.tl.core.height.get(), workspace_rect.height()); + + tassert_eq!(window.tl.server.node_absolute_position(), workspace_rect); Ok(()) } diff --git a/src/it/tests/t0003_multi_window.rs b/src/it/tests/t0003_multi_window.rs index 3fbf599c..db726f90 100644 --- a/src/it/tests/t0003_multi_window.rs +++ b/src/it/tests/t0003_multi_window.rs @@ -11,7 +11,7 @@ testcase!(); /// Create and map two surfaces async fn test(run: Rc) -> Result<(), TestError> { - run.backend.install_default()?; + let ds = run.create_default_setup().await?; let client = run.create_client().await?; @@ -21,17 +21,30 @@ async fn test(run: Rc) -> Result<(), TestError> { let window2 = client.create_window().await?; window2.map().await?; - let otop = 2 * run.state.theme.title_plus_underline_height(); + let workspace_rect = ds.output.workspace_rect.get(); let bw = run.state.theme.sizes.border_width.get(); + let child_width = (workspace_rect.width() - bw) / 2; tassert_eq!( window.tl.server.node_absolute_position(), - Rect::new_sized(0, otop, (800 - bw) / 2, 600 - otop).unwrap() + Rect::new_sized( + workspace_rect.x1(), + workspace_rect.y1(), + child_width, + workspace_rect.height(), + ) + .unwrap() ); tassert_eq!( window2.tl.server.node_absolute_position(), - Rect::new_sized((800 - bw) / 2 + bw, otop, (800 - bw) / 2, 600 - otop).unwrap() + Rect::new_sized( + workspace_rect.x1() + child_width + bw, + workspace_rect.y1(), + child_width, + workspace_rect.height(), + ) + .unwrap() ); Ok(()) diff --git a/src/it/tests/t0007_subsurface/screenshot_1.qoi b/src/it/tests/t0007_subsurface/screenshot_1.qoi index 230c0408f7411f5ac77c386a080c686f2646c3c0..b5954651730842344f9fa6b0313a8e3495e2b43f 100644 GIT binary patch delta 26 dcmX?Wf6RV^5#we>rp2<89hhElzG8p?MgV{*2Rr}( delta 35 ncmX?Rf7X735hKe#B_*ZFj*PdNuWanttjf4Smh%<^1TX>s>&ptK diff --git a/src/it/tests/t0007_subsurface/screenshot_2.qoi b/src/it/tests/t0007_subsurface/screenshot_2.qoi index 722271f61949f939e280e154ee9a514d0b14314c..718d5c298a2d70f46b17a4027f647d31295d9c97 100644 GIT binary patch delta 26 dcmbPXJJEK75#we>raYO+4oojNUob!bBLH#!29^K- delta 35 mcmbPeJHvK^5hKe#B_*ZFj*PdNuWanttjbs|!+C=N0vG|>nhF5` diff --git a/src/it/tests/t0014_container_scroll_focus.rs b/src/it/tests/t0014_container_scroll_focus.rs index 0186cbaf..dccd1096 100644 --- a/src/it/tests/t0014_container_scroll_focus.rs +++ b/src/it/tests/t0014_container_scroll_focus.rs @@ -48,13 +48,18 @@ async fn test(run: Rc) -> TestResult { let mono_container = w_mono2.tl.container_parent()?; let container_pos = mono_container.tl_data().pos.get(); - let w_mono1_title = mono_container.render_data.borrow_mut().title_rects[0] - .move_(container_pos.x1(), container_pos.y1()); - ds.mouse.abs( - &ds.connector, - w_mono1_title.x1() as _, - w_mono1_title.y1() as _, - ); + let (tab_x, tab_y) = { + let tab_bar = mono_container.tab_bar.borrow(); + let Some(tab_bar) = tab_bar.as_ref() else { + bail!("no tab bar"); + }; + let w_mono1_title = &tab_bar.entries[0]; + ( + container_pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2, + container_pos.y1() + tab_bar.height / 2, + ) + }; + ds.mouse.abs(&ds.connector, tab_x as _, tab_y as _); client.sync().await; tassert!(enters.next().is_err()); diff --git a/src/it/tests/t0015_scroll_partial.rs b/src/it/tests/t0015_scroll_partial.rs index c6cf49b7..f5cb6e3c 100644 --- a/src/it/tests/t0015_scroll_partial.rs +++ b/src/it/tests/t0015_scroll_partial.rs @@ -26,12 +26,18 @@ async fn test(run: Rc) -> TestResult { let container = w_mono2.tl.container_parent()?; let pos = container.tl_data().pos.get(); - let w_mono1_title = container.render_data.borrow_mut().title_rects[0].move_(pos.x1(), pos.y1()); - ds.mouse.abs( - &ds.connector, - w_mono1_title.x1() as f64, - w_mono1_title.y1() as f64, - ); + let (tab_x, tab_y) = { + let tab_bar = container.tab_bar.borrow(); + let Some(tab_bar) = tab_bar.as_ref() else { + bail!("no tab bar"); + }; + let w_mono1_title = &tab_bar.entries[0]; + ( + pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2, + pos.y1() + tab_bar.height / 2, + ) + }; + ds.mouse.abs(&ds.connector, tab_x as f64, tab_y as f64); client.sync().await; let enters = dss.kb.enter.expect()?; diff --git a/src/it/tests/t0020_surface_offset/screenshot_1.qoi b/src/it/tests/t0020_surface_offset/screenshot_1.qoi index eef5f37a645f72caeef422b0da0ac30b6bbd7161..4c826f86d5ac9208c42f2383ea7cddf791b8efbe 100644 GIT binary patch delta 37 mcmX?Wf69J?DdT1ZCOf{#0gNS#7dFQWr1FE9oL3kifDr)hJqi*4 delta 38 qcmX?Qf7X73DI=r!WJku^OjnFHYcP8AF`n4`f2$ui= delta 41 mcmbPXJKJ``e@5}ijEqZo?-~7tffuHm85tkSa6Vvw07d}03KB^G diff --git a/src/it/tests/t0028_top_level_restacking/screenshot_1.qoi b/src/it/tests/t0028_top_level_restacking/screenshot_1.qoi index f7bf53bfb47e66ca0eb0cb80d5c67eab09895f17..9f5fca3ccec0e941a93041aab8fd687decd79b2d 100644 GIT binary patch literal 11083 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%d81SF9K zM%kkwFd71*A%GSFFaFuPdqKn=nLy}E(h!<>{-J;WVa4$+=|ga14jDlV8WsEdcQgz} z!vK^Uh)4%_#6b%G{)ObfchZN9{!$=x=pVy>XjAEyI3#aEjXY!kv1e5H@87?pVRA=& zG(r6R`*$=qjOGS#i94DbMsvexeK}e&{vE9t!R^G+igC1J9IY5fTLq)7g3(sNXsckf zRRC@jjCPkn!7w^_G}=Z6rGwGj015@#=7zryu#8huB>4C5B@81+#XvDR8ZM*hfV6Y~ nQVpBA0)-uEe2D5%ax?@+Ltr!nMnhmU1V%%EDj{%y0Rk8S?XmR| literal 11228 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%d81SF9K zM%kkwFd71*A%GSFFaFuPdqKn=nLy}E(h!<>{-J+LN=gt-x13S-9JXqb$K3AieUW#`cZF`6Jo6U1n4xFbDUjbdr- zj24fh#p7u4I9fc87LVXQ)@aY`@87?pxnVRnjOK>XqF}Tr01w2D_8Ug~4Up-f(TZ{8 zR*Zk2V04wq6#D!362@4>s2HfhF&ZudnGQfP@b@qA^XI7QA-qv?Gz3ONU^E0qLtr!n MhF=JLV}Jlg0J2guZvX%Q diff --git a/src/it/tests/t0028_top_level_restacking/screenshot_2.qoi b/src/it/tests/t0028_top_level_restacking/screenshot_2.qoi index b454acd32bb155d7ad0c8228081cd1b491d69a6a..aaf1b1084b90ad1cae0b4bdad7f0e3589fbb1644 100644 GIT binary patch literal 11083 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%d81SF9K zM%kkwFd71*A%GSFFT^1Nk4zx+C6f7v{{4rQ#ka%{!Hqd&1Tk<_?C;;vFc=L3P;MY0 z9sIL*_xk$)oSWW>BjqJ&$^`#0{Ac)kM;ek%Q4BU16$VAoXqbRP0aRj*CJ4;jHyS3R zVKQ1mjFu3$#1D-Y1*1j5Xi+d)6pR)Hquq(WfB%ja1*1j5Xi+d)6pR)HNP|_QZRF85 z@@U04S~1e1Vg!fk-$%$r44FcI|6Y{vmOQj$6`);AV`9U6Q6K42-aZ>A(N5 zY7Zr-u_+xDlRg9wgTGI}>GB;lgbw{vQd0VR2b$E8jXY#LDhx`rbPE%3qX#J_P_oBA zhW`xM!f0+7ak&8; z?SCI352leR^!M*2&@cp8H_C*V0ayr>w!i~cqv}B6F&b9%N(Z3O{`;5s`EykD5Z)*` Y8UmvsFd71*Aut*O!!HEBF+cz#0NCX-ZvX%Q diff --git a/src/it/tests/t0029_double_click_float/screenshot_1.qoi b/src/it/tests/t0029_double_click_float/screenshot_1.qoi index dd974ccffda932661558262ad0c65bd5f05f7bed..e08dc52581fd8eb9989908db258884c870eabefa 100644 GIT binary patch literal 9988 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%d81SF9K zM%kkwFd71*Aut*Oqai@s5O^mJG5?VXguX;F|IokxuEY9gL;}Pza2s15hZ8mItHhU^E?oLSQrh$W8~45FmHR9-@1c91Vfd5Eu=C(GVC7fzc44 LN(dZafB;4S<*jIf delta 63 ycmZqin`OJfl#x+)~z diff --git a/src/it/tests/t0029_double_click_float/screenshot_2.qoi b/src/it/tests/t0029_double_click_float/screenshot_2.qoi index f49edd4d3627d804802ad5f24047b69f68aebc11..e08dc52581fd8eb9989908db258884c870eabefa 100644 GIT binary patch literal 9988 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%d81SF9K zM%kkwFd71*Aut*Oqai@s5O^mJG5?VXguX;F|IokxuEY9gL;}Pza2s15hZ8mItHhU^E?oLSQrh$W8~45FmHR9-@1c91Vfd5Eu=C(GVC7fzc44 LN(dZafB;4S<*jIf literal 10118 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%d81SF9K zM%kkwFd71*Aut*OBPj%){j+!Xf_V6m3532R4WWtW9}$CVM$^G)Iv7m{ zpb!{M2cS?GEe}T1!Du=Fg}`V!0ENP6c`%v|M$-W(1V+;VC=^D^gOQjHKr#RKFY!|$ esOllSQF1f{MnhmU1V%$(Gz5lU2z+CJ07d}izNMo8 diff --git a/src/it/tests/t0037_toplevel_drag/screenshot_2.qoi b/src/it/tests/t0037_toplevel_drag/screenshot_2.qoi index b982600106353e9f8437aef8646a02393436a2f2..36c68e4e768d40bb17217fc1cab4cb1fd64c9431 100644 GIT binary patch delta 26 dcmbPbJKc7JDdT1Zrb3y?4oojNUob!bBLH(B2BQD~ delta 31 icmbPkJIi*1DI=r!WJku^OjnFHYcSTyaNb~m07d|tO$WLF diff --git a/src/it/tests/t0038_subsurface_parent_state/screenshot_1.qoi b/src/it/tests/t0038_subsurface_parent_state/screenshot_1.qoi index 988bc7671d6457cc816bad517a2afbcf0c715f82..e6f6db7440dae2befd5a205742e4fffcffbd4624 100644 GIT binary patch delta 26 dcmX?Yf5Lu)DdT1ZrbV)o3pieIK4X9YMgW4=2X6oX delta 31 icmX?Mf7*V7DI=r!IjAa diff --git a/src/it/tests/t0038_subsurface_parent_state/screenshot_2.qoi b/src/it/tests/t0038_subsurface_parent_state/screenshot_2.qoi index a750940444d66d00a431c783a04ab238c4e003f5..9abc8de3ef07210581c75e70ddaecda6139ed9e5 100644 GIT binary patch delta 26 dcmX?af5v`;DdT1Zre(5|9hhElzG8p?MgW0I2T1?` delta 31 icmX?Of8KtBDI=r!WJku^OjnFHYcOt><-ElJ0gM2u#t2{l diff --git a/src/it/tests/t0039_alpha_modifier/screenshot_1.qoi b/src/it/tests/t0039_alpha_modifier/screenshot_1.qoi index 8fe5d0b20afa62ec1d7d2d78e5404f23f866b3a7..80a29c84096cdf84b80c56efe5beec7ec389336a 100644 GIT binary patch delta 26 dcmX?Sf5v`;DdT1Zrlqoz3pieK9$ diff --git a/src/it/tests/t0041_input_method/screenshot_1.qoi b/src/it/tests/t0041_input_method/screenshot_1.qoi index d25fcf64e3e23045b297675595be3d4cd4e918fe..cd07ecd40de1de1351fc41f64764db24de49845f 100644 GIT binary patch delta 26 dcmbPkJH>W`DdT1ZraYO+223wFUob!bBLH!U28sXx delta 31 icmbPYJKc7JDI=r!WJku^OjnFHYcN*JaNb~m07d|sbqBEk diff --git a/src/it/tests/t0041_input_method/screenshot_2.qoi b/src/it/tests/t0041_input_method/screenshot_2.qoi index 7f93231a07c6dce95b1d4600a3bdcce74d3fb83a..d76ea9a00c0c4557432c1dcb10f2208b136f39a8 100644 GIT binary patch delta 26 dcmX?Wf69J?DdT1ZrX{kI4VYeVUSog&MgV_82Oj_c delta 31 icmX?Qf7X73DI=r!WJku^OjnFHYcOt<W`DdT1ZraYO+223wFUob!bBLH!U28sXx delta 31 icmbPYJKc7JDI=r!WJku^OjnFHYcN*JaNb~m07d|sbqBEk diff --git a/src/it/tests/t0042_toplevel_select/screenshot_1.qoi b/src/it/tests/t0042_toplevel_select/screenshot_1.qoi index 6423ef6db9291f5bd017c8ce96b8f9fadefd399e..6d57d140b27509b0ff6f1292276322b6a1f5e261 100644 GIT binary patch delta 33 mcmdlKvOZ*kDI@>CBH0Ol!QjR}dv~wR2bnU}H*e5rVgvv=(GkP| literal 10802 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN$au-i?3u z?p}xfDJdyI`7m+mLs0cMMn(SKK!k=7G@PUl!C3!_WGBD{p{eW!@^=|iKT@pm*O z`~_v7(UdTn68?^+1Zb@=T04xE3E-qKT04xE3E+e|>mU^Ewu=7P~&Fq#WSbHQjX7|jKvxnMLGjOK#T PTripoNXZ2Z5Wolk7CT)9 diff --git a/src/it/tests/t0042_toplevel_select/screenshot_2.qoi b/src/it/tests/t0042_toplevel_select/screenshot_2.qoi index 823fd7503f7b28aec8d0b563df3c7e482a1b909f..478b3c4336b87cf8a13500c8c5b3f0f93d2b3fba 100644 GIT binary patch delta 44 scmdlGvOZ*kDWjn2Uog1w&)(hZ(7z(t37ZcxrK@iiV6I~1V1NKd00Qz6^#A|> literal 10800 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%XB5blkC z_U>MX#GxFhnj6xiB7gthzzhu|XgEn9g0Y~<>W1{8e?_trpwb9Io>7q-qoMKlhV-G) zl<;>nCHx&t3E%>8G$o9t1aLwaEffBZri9UyFj^jt=7P~&Fq#WSbHQjX7|jKvxnMLG zjOK#TTripoMsvYvE*Q-Pqq$%-7mVhD(OfW^3r2InXf7Dd1*5rOG#8BKg3(+snhQpA L0SUQ)0Rk8SQYl=x diff --git a/src/it/tests/t0042_toplevel_select/screenshot_3.qoi b/src/it/tests/t0042_toplevel_select/screenshot_3.qoi index 823fd7503f7b28aec8d0b563df3c7e482a1b909f..478b3c4336b87cf8a13500c8c5b3f0f93d2b3fba 100644 GIT binary patch delta 44 scmdlGvOZ*kDWjn2Uog1w&)(hZ(7z(t37ZcxrK@iiV6I~1V1NKd00Qz6^#A|> literal 10800 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%XB5blkC z_U>MX#GxFhnj6xiB7gthzzhu|XgEn9g0Y~<>W1{8e?_trpwb9Io>7q-qoMKlhV-G) zl<;>nCHx&t3E%>8G$o9t1aLwaEffBZri9UyFj^jt=7P~&Fq#WSbHQjX7|jKvxnMLG zjOK#TTripoMsvYvE*Q-Pqq$%-7mVhD(OfW^3r2InXf7Dd1*5rOG#8BKg3(+snhQpA L0SUQ)0Rk8SQYl=x diff --git a/src/it/tests/t0042_toplevel_select/screenshot_4.qoi b/src/it/tests/t0042_toplevel_select/screenshot_4.qoi index 714222f1611628b04d16f12865fff4e6bb6938c6..07dd87fbbc7a82e14db208be44aea30e480ffaf1 100644 GIT binary patch delta 34 ncmX@^y~ulmDI<^RUog1w&)(f@a~qR|^5%I=3XGHYsd54UJfjdq literal 9671 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%XB5blkC z_U>MX#GxFhnj6xiB7gthzzhu|XgEn9g0Y~<>W1_oC<~(E(HZJ%AGelo(A9qv-*f6u{JIdKgU);Di7rM$^M+dVnScFg2PU zM$-d0AwY@I^e~zpph*Etji!gu^Z-r>P+~MajHU-@QUFt<>0vZIfD;0g7)=kO=>eJ) zz|?4Z7)=l0ga9Q*)5B) -> TestResult { let output_damage = connector_data.damage.borrow(); tassert!(!output_damage.is_empty()); - // Buffer damage is transformed by the damage matrix which includes the surface position - // The buffer damage (0,0,1,1) should be transformed to surface coordinates - let expected_buffer_damage = buffer_damage.move_(surface_pos.x1(), surface_pos.y1()); + // The test window maps its 1x1 buffer through a viewport to the full window size. + let expected_buffer_damage = surface_pos; // Find the exact output damage that matches our expected buffer damage let mut found_exact_buffer_damage = false; @@ -331,10 +330,12 @@ async fn test(run: Rc) -> TestResult { // Test 7: Check output damage from existing window's viewport (which already has scaling) connector_data.damage.borrow_mut().clear(); - // The existing window was created with create_surface_ext() which automatically creates a viewport - // Let's verify that the viewport's existing scaling affects buffer damage correctly - // First, let's modify the viewport scaling that already exists on the window - window.surface.viewport.set_destination(150, 100)?; // Change scaling to 150x100 + // The existing window was created with create_surface_ext() which automatically creates a viewport. + // Commit the viewport size change separately; that commit intentionally damages the old/new extents. + window.surface.viewport.set_destination(150, 100)?; + window.surface.commit()?; + client.sync().await; + connector_data.damage.borrow_mut().clear(); // Add buffer damage to test viewport scaling coordinate transformation window.surface.damage_buffer(0, 0, 1, 1)?; // Damage entire 1x1 buffer @@ -346,8 +347,8 @@ async fn test(run: Rc) -> TestResult { let output_damage = connector_data.damage.borrow(); tassert!(!output_damage.is_empty()); - // With viewporter scaling, the 1x1 buffer damage should scale to 150x100 - // and be moved by surface position (0, 36) to get output coordinates (0, 36, 150, 136) + // With viewporter scaling, the 1x1 buffer damage should scale to the viewport destination. + let surface_pos = window.surface.server.buffer_abs_pos.get(); let expected_scaled_damage = Rect::new_sized(0, 0, 150, 100).unwrap(); let expected_output_damage = expected_scaled_damage.move_(surface_pos.x1(), surface_pos.y1()); @@ -402,8 +403,9 @@ async fn test(run: Rc) -> TestResult { rotation_window.map().await?; client.sync().await; - // Disable viewporter by setting destination to 0x0 to rely purely on buffer dimensions - rotation_window.surface.viewport.set_destination(0, 0)?; // Disable viewporter + // Disable viewporter to rely purely on buffer dimensions. + rotation_window.surface.viewport.unset_source()?; + rotation_window.surface.viewport.unset_destination()?; // Use a rectangular buffer (4x2) so rotation has a visible geometric effect // Attach AFTER mapping to avoid being overwritten by map()'s single-pixel buffer diff --git a/src/tree/container.rs b/src/tree/container.rs index b81f2e85..44a6a778 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -32,6 +32,7 @@ use { numcell::NumCell, on_drop_event::OnDropEvent, rc_eq::rc_eq, + scroller::Scroller, threshold_counter::ThresholdCounter, }, }, @@ -150,6 +151,7 @@ pub struct ContainerNode { pub child_removed: Rc, pub all_children_resized: Rc, pub tab_bar: RefCell>, + scroll: Scroller, pub update_tab_textures_scheduled: Cell, pub ephemeral: Cell, } @@ -266,6 +268,7 @@ impl ContainerNode { child_removed: state.lazy_event_sources.create_source(), all_children_resized: state.post_layout_event_sources.create_source(), tab_bar: RefCell::new(None), + scroll: Default::default(), update_tab_textures_scheduled: Cell::new(false), ephemeral: Cell::new(Ephemeral::Off), }); @@ -793,6 +796,18 @@ impl ContainerNode { self.activate_child2(child, false); } + fn activate_child_from_input( + self: &Rc, + child: &NodeRef, + seat: &Rc, + ) { + self.activate_child(child); + child + .node + .clone() + .node_do_focus(seat, Direction::Unspecified); + } + fn activate_child2(self: &Rc, child: &NodeRef, preserve_focus: bool) { if let Some(mc) = self.mono_child.get() { if mc.node.node_id() == child.node.node_id() { @@ -1519,7 +1534,7 @@ impl ContainerNode { fn button( self: Rc, id: CursorType, - _seat: &Rc, + seat: &Rc, _time_usec: u64, pressed: bool, button: u32, @@ -1549,7 +1564,7 @@ impl ContainerNode { if let Some(child) = children.get(&child_id) { let child_ref = child.to_ref(); drop(children); - self.activate_child(&child_ref); + self.activate_child_from_input(&child_ref, seat); } return; } @@ -2066,31 +2081,33 @@ impl Node for ContainerNode { self.button(id, seat, time_usec, state == ButtonState::Pressed, button); } - fn node_on_axis_event(self: Rc, _seat: &Rc, event: &PendingScroll) { + fn node_on_axis_event(self: Rc, seat: &Rc, event: &PendingScroll) { if self.mono_child.is_none() { return; } - // Use vertical scroll (index 1) to switch tabs. - let v = match event.v120[1].get() { - Some(v) if v != 0 => v, + let steps = match self.scroll.handle(event) { + Some(steps) => steps, _ => return, }; - let mono = match self.mono_child.get() { + let mut target = match self.mono_child.get() { Some(m) => m, None => return, }; - let next = if v > 0 { - // Scroll down → next tab. - mono.next().or_else(|| self.children.first()) - } else { - // Scroll up → previous tab. - mono.prev().or_else(|| self.children.last()) - }; - if let Some(next) = next { - if next.node.node_id() != mono.node.node_id() { - self.activate_child(&next); + let current_id = target.node.node_id(); + for _ in 0..steps.abs() { + let next = if steps > 0 { + target.next().or_else(|| self.children.first()) + } else { + target.prev().or_else(|| self.children.last()) + }; + match next { + Some(next) => target = next, + None => break, } } + if target.node.node_id() != current_id { + self.activate_child_from_input(&target, seat); + } } fn node_on_leave(&self, seat: &WlSeatGlobal) { From 5db14936e715fa58e4a6697adaf255e5a223457b Mon Sep 17 00:00:00 2001 From: atagen Date: Sun, 31 May 2026 17:12:49 +1000 Subject: [PATCH 2/2] fix: restore focus after backend visibility resumes --- src/it/tests/t0022_toplevel_suspended.rs | 23 ++++++++++++-- src/tree/display.rs | 40 ++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/it/tests/t0022_toplevel_suspended.rs b/src/it/tests/t0022_toplevel_suspended.rs index 1fdacb1a..524856e3 100644 --- a/src/it/tests/t0022_toplevel_suspended.rs +++ b/src/it/tests/t0022_toplevel_suspended.rs @@ -2,7 +2,7 @@ use { crate::{ ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED, it::{ - test_error::TestResult, + test_error::{TestErrorExt, TestResult}, test_utils::{ test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt, }, @@ -10,7 +10,7 @@ use { }, }, isnt::std_1::collections::IsntHashSetExt, - std::rc::Rc, + std::{rc::Rc, time::Duration}, }; testcase!(); @@ -19,6 +19,7 @@ async fn test(run: Rc) -> TestResult { let ds = run.create_default_setup().await?; let client = run.create_client().await?; + let default_seat = client.get_default_seat().await?; let win1 = client.create_window().await?; win1.set_color(255, 0, 0, 255); @@ -44,5 +45,23 @@ async fn test(run: Rc) -> TestResult { client.sync().await; tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED)); + let leaves = default_seat.kb.leave.expect()?; + let enters = default_seat.kb.enter.expect()?; + + run.cfg.set_idle(Duration::from_micros(100))?; + run.cfg.set_idle_grace_period(Duration::from_secs(0))?; + run.state.wheel.timeout(3).await?; + + client.sync().await; + tassert!(win2.tl.core.states.borrow().contains(&STATE_SUSPENDED)); + let leave = leaves.next().with_context(|| "no leave on suspend")?; + tassert_eq!(leave.surface, win2.surface.id); + + ds.mouse.rel(1.0, 1.0); + client.sync().await; + tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED)); + let enter = enters.next().with_context(|| "no enter on restore")?; + tassert_eq!(enter.surface, win2.surface.id); + Ok(()) } diff --git a/src/tree/display.rs b/src/tree/display.rs index 440916bf..26b31a88 100644 --- a/src/tree/display.rs +++ b/src/tree/display.rs @@ -8,18 +8,25 @@ use { renderer::Renderer, state::State, tree::{ - FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, - OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination, + Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, + NodeLocation, OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination, WorkspaceNodeId, walker::NodeVisitor, }, utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList}, }, - std::{cell::Cell, ops::Deref, rc::Rc}, + std::{ + cell::{Cell, RefCell}, + mem, + ops::Deref, + rc::{Rc, Weak}, + }, }; pub struct DisplayNode { pub id: NodeId, pub extents: Cell, + visible: Cell, + suspend_restore_kb_foci: RefCell, Weak)>>, pub outputs: CopyHashMap>, pub stacked: Rc>>, pub stacked_above_layers: Rc>>, @@ -31,6 +38,8 @@ impl DisplayNode { let slf = Self { id, extents: Default::default(), + visible: Default::default(), + suspend_restore_kb_foci: Default::default(), outputs: Default::default(), stacked: Default::default(), stacked_above_layers: Default::default(), @@ -71,6 +80,17 @@ impl DisplayNode { pub fn update_visible(&self, state: &State) { let visible = state.root_visible(); + let was_visible = self.visible.replace(visible); + if !visible && was_visible { + let mut foci = self.suspend_restore_kb_foci.borrow_mut(); + foci.clear(); + for seat in state.globals.seats.lock().values() { + let node = seat.get_keyboard_node(); + if node.node_id() != self.id { + foci.push((seat.clone(), Rc::downgrade(&node))); + } + } + } for output in self.outputs.lock().values() { output.update_visible(); } @@ -82,6 +102,20 @@ impl DisplayNode { for seat in state.globals.seats.lock().values() { seat.set_visible(visible); } + if visible && !was_visible { + for (seat, node) in mem::take(&mut *self.suspend_restore_kb_foci.borrow_mut()) { + if seat.get_keyboard_node().node_id() == self.id { + if let Some(node) = node.upgrade() + && node.node_visible() + { + seat.focus_node(node); + } else { + seat.get_fallback_output() + .take_keyboard_navigation_focus(&seat, Direction::Unspecified); + } + } + } + } if visible { state.damage(self.extents.get()); }