From 17e649dd322db278e23f37f4ddf9df58c6db186a Mon Sep 17 00:00:00 2001 From: "Pedro M. de Echanove Pasquin" Date: Tue, 6 Oct 2026 23:09:43 +0200 Subject: [PATCH] The on-screen keyboard is capped at 40 % of the screen height, and the runtime publishes the logical size of the output the main surface is on The keyboard. The keys of the on-screen panel are as tall as one unit of the widest row is wide, so the panel's height follows its width. On a portrait phone that is about a quarter of the screen; turned to landscape the width becomes the long side, the keys grow with it and the six rows of a terminal keyboard take more than the screen. `Panel::height_within( width, scale, screen_height )` keeps `height` within 40 % of the screen height, the new `MAX_HEIGHT_FRACTION`, and `place_osk` uses it for the keyboard of a surface of ltk's own. `set_rect` no longer assumes it was given the natural height: the key height is the smaller of the unit and what the rectangle left after the padding and the gaps shares among the rows, so a capped panel gets wide and low keys, as the keyboards of other platforms have in landscape, while the key widths and the centring of the narrower rows are as before. A portrait screen is not affected, the natural height being below the cap there. A host that lays the panel out itself, as a compositor does, calls `height_within` for the height it reserves and `set_rect` with the rectangle it gets, so both agree. The output's size. `ltk::output_logical_size()` returns the logical size of the screen the main surface is on, or `None` before the compositor has described one, and `set_output_logical_size` records it. `viewport_size` follows the window, which is the wrong reference for a thing that has to be sized relative to the display, such as a window that must not be wider than the screen. The runtime now publishes the size when an output appears, changes or goes away, and when the main surface enters one, preferring the output of the main surface to the first one listed, and taking the current mode over the scale when the compositor sent no logical size. `window_fit` shares that lookup, `output_logical_size`, with what it did before, so the bounds it keeps a window size hint inside are unchanged. Not done. The 40 % is a constant and not a setting, the key labels are not scaled to the lower keys, and nothing offers the keyboard at the side of the screen in landscape, which would give the application more room on a wide display. --- src/event_loop/handlers.rs | 15 +++++++++++++-- src/event_loop/osk.rs | 2 +- src/event_loop/window_fit.rs | 21 ++++++++++++++++++--- src/lib.rs | 1 + src/osk/mod.rs | 13 +++++++++++-- src/types.rs | 21 +++++++++++++++++++++ 6 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/event_loop/handlers.rs b/src/event_loop/handlers.rs index 910afba..823ad62 100644 --- a/src/event_loop/handlers.rs +++ b/src/event_loop/handlers.rs @@ -154,7 +154,13 @@ impl CompositorHandler for AppData // exclusively by input events, poll_external messages, and configure events. } - fn surface_enter( &mut self, _: &Connection, _: &QueueHandle, _: &WlSurface, _: &WlOutput ) {} + fn surface_enter( &mut self, _: &Connection, _: &QueueHandle, surface: &WlSurface, output: &WlOutput ) + { + if self.main.surface.try_wl_surface() == Some( surface ) + { + self.publish_output_size( Some( output ) ); + } + } fn surface_leave( &mut self, _: &Connection, _: &QueueHandle, _: &WlSurface, _: &WlOutput ) {} } @@ -333,6 +339,7 @@ impl OutputHandler for AppData fn new_output( &mut self, _: &Connection, qh: &QueueHandle, output: WlOutput ) { + self.publish_output_size( None ); // If we were waiting for an output to assign the layer surface to, create it now. // Same for any overlays that were created before an output existed. if let Some( ref layer_shell ) = self.layer_shell @@ -394,7 +401,10 @@ impl OutputHandler for AppData } } - fn update_output( &mut self, _: &Connection, _: &QueueHandle, _: WlOutput ) {} + fn update_output( &mut self, _: &Connection, _: &QueueHandle, _: WlOutput ) + { + self.publish_output_size( None ); + } fn output_destroyed( &mut self, _: &Connection, _: &QueueHandle, output: WlOutput ) { @@ -403,6 +413,7 @@ impl OutputHandler for AppData { self.main_output = None; } + self.publish_output_size( None ); } } diff --git a/src/event_loop/osk.rs b/src/event_loop/osk.rs index 47ab184..e24540a 100644 --- a/src/event_loop/osk.rs +++ b/src/event_loop/osk.rs @@ -19,7 +19,7 @@ use crate::types::{ Point, Rect }; pub( crate ) fn place_osk( osk: &mut Option, pw: u32, ph: u32, sf: f32 ) -> f32 { let Some( panel ) = osk.as_mut() else { return 0.0 }; - let h = panel.height( pw as f32, sf ).min( ph as f32 ); + let h = panel.height_within( pw as f32, sf, ph as f32 ); panel.set_rect( Rect { x: 0.0, y: ph as f32 - h, width: pw as f32, height: h }, sf ); h } diff --git a/src/event_loop/window_fit.rs b/src/event_loop/window_fit.rs index 9f9379a..61e0b2b 100644 --- a/src/event_loop/window_fit.rs +++ b/src/event_loop/window_fit.rs @@ -3,6 +3,7 @@ //! Keeps [`App::window_size_hint`](crate::App::window_size_hint) inside the screen. +use smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput; use smithay_client_toolkit::shell::xdg::window::Window; use crate::app::App; @@ -42,15 +43,29 @@ impl AppData fn output_bounds( &self ) -> Option<( u32, u32 )> { - let info = self.output_state.outputs().find_map( |o| self.output_state.info( &o ) )?; + let ( w, h ) = self.output_logical_size( None )?; + Some( ( ( w as f32 * OUTPUT_SHARE ) as u32, ( h as f32 * OUTPUT_SHARE ) as u32 ) ) + } + + fn output_logical_size( &self, preferred: Option<&WlOutput> ) -> Option<( u32, u32 )> + { + let info = preferred + .or( self.main_output.as_ref() ) + .and_then( |o| self.output_state.info( o ) ) + .or_else( || self.output_state.outputs().find_map( |o| self.output_state.info( &o ) ) )?; let ( w, h ) = info.logical_size.or_else( || { let mode = info.modes.iter().find( |m| m.current )?; let scale = info.scale_factor.max( 1 ); Some( ( mode.dimensions.0 / scale, mode.dimensions.1 / scale ) ) } )?; - if w <= 0 || h <= 0 { return None; } - Some( ( ( w as f32 * OUTPUT_SHARE ) as u32, ( h as f32 * OUTPUT_SHARE ) as u32 ) ) + ( w > 0 && h > 0 ).then_some( ( w as u32, h as u32 ) ) + } + + pub( crate ) fn publish_output_size( &self, preferred: Option<&WlOutput> ) + { + let ( w, h ) = self.output_logical_size( preferred ).unwrap_or( ( 0, 0 ) ); + crate::types::set_output_logical_size( w, h ); } } diff --git a/src/lib.rs b/src/lib.rs index 40efce1..dc4be94 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -358,6 +358,7 @@ pub use types::{ density, set_density }; pub use types::{ set_text_scale, text_scale }; pub use types::{ widget_scaling, set_widget_scaling }; pub use types::{ Orientation, orientation, viewport_size, set_viewport_size }; +pub use types::{ output_logical_size, set_output_logical_size }; pub use widget::{ Element, button, icon_button, text_edit, image as img_widget, text, container }; pub use text_shaping::measure_text; pub use widget::rich_text::{ rich_text, RichText, LinkSpan }; diff --git a/src/osk/mod.rs b/src/osk/mod.rs index 6119461..807f75b 100644 --- a/src/osk/mod.rs +++ b/src/osk/mod.rs @@ -24,6 +24,7 @@ const SHIFT_DOUBLE_TAP: Duration = Duration::from_millis( 350 ); const GAP: f32 = 6.0; const ROW_GAP: f32 = 2.0 * GAP; const PADDING: f32 = 6.0; +const MAX_HEIGHT_FRACTION: f32 = 0.4; /// What the field being typed into is for; picks the extra row above the layout. #[ derive( Clone, Copy, Debug, PartialEq, Eq, Default ) ] @@ -228,6 +229,12 @@ impl Panel rows * self.key_unit( width, scale ) + ( ( rows - 1.0 ).max( 0.0 ) * ROW_GAP + 2.0 * PADDING ) * scale } + /// [`Self::height`] kept within 40 % of `screen_height`, so a wide screen gets short keys instead of a keyboard that covers it. + pub fn height_within( &self, width: f32, scale: f32, screen_height: f32 ) -> f32 + { + self.height( width, scale ).min( screen_height * MAX_HEIGHT_FRACTION ) + } + /// Width of one key unit of the layout's rows, `width` pixels wide: the /// widest row fills the panel. Keys are as tall as one unit is wide. fn key_unit( &self, width: f32, scale: f32 ) -> f32 @@ -250,7 +257,10 @@ impl Panel let gap = GAP * scale; let row_gap = ROW_GAP * scale; let pad = PADDING * scale; - let key_h = self.key_unit( rect.width, scale ); + let unit = self.key_unit( rect.width, scale ); + let rows_n = ( self.bar_rows().len() + self.layout().rows( &self.layer ).len() ) as f32; + let fit = ( rect.height - 2.0 * pad - ( rows_n - 1.0 ).max( 0.0 ) * row_gap ) / rows_n.max( 1.0 ); + let key_h = unit.min( fit.max( 1.0 ) ); let avail = rect.width - 2.0 * pad; let mut y = rect.y + pad; @@ -266,7 +276,6 @@ impl Panel let rows: Vec> = self.layout().rows( &self.layer ).iter() .map( |row| row.iter().map( |k| self.labelled( k ) ).collect() ) .collect(); - let unit = key_h; for row in &rows { let units: f32 = row.iter().map( |k| k.width ).sum(); diff --git a/src/types.rs b/src/types.rs index ec76a60..e23f26f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -790,6 +790,27 @@ pub fn viewport_size() -> ( u32, u32 ) ( VIEWPORT_W.load( Ordering::Relaxed ), VIEWPORT_H.load( Ordering::Relaxed ) ) } +static OUTPUT_W: AtomicU32 = AtomicU32::new( 0 ); +static OUTPUT_H: AtomicU32 = AtomicU32::new( 0 ); + +/// Record the logical size of the output the main surface is on. The +/// runtime calls this when outputs appear, change or go away, and when the +/// main surface enters one. `( 0, 0 )` clears it. +pub fn set_output_logical_size( width: u32, height: u32 ) +{ + OUTPUT_W.store( width, Ordering::Relaxed ); + OUTPUT_H.store( height, Ordering::Relaxed ); +} + +/// Logical size of the screen the main surface is on, or `None` before the +/// compositor has described one. Unlike [`viewport_size`] it does not follow +/// the window, so it can size things relative to the display. +pub fn output_logical_size() -> Option<( u32, u32 )> +{ + let size = ( OUTPUT_W.load( Ordering::Relaxed ), OUTPUT_H.load( Ordering::Relaxed ) ); + ( size.0 > 0 && size.1 > 0 ).then_some( size ) +} + /// Orientation of the main surface: [`Orientation::Landscape`] when wider /// than tall, [`Orientation::Portrait`] otherwise (square counts as /// portrait, matching [`Length::orient`]'s resolution rule). Usable