diff options
Diffstat (limited to 'crates/wayland/src/state')
| -rw-r--r-- | crates/wayland/src/state/desktop_icons.rs | 25 | ||||
| -rw-r--r-- | crates/wayland/src/state/lifecycle.rs | 29 | ||||
| -rw-r--r-- | crates/wayland/src/state/menu.rs | 14 | ||||
| -rw-r--r-- | crates/wayland/src/state/mod.rs | 7 |
4 files changed, 71 insertions, 4 deletions
diff --git a/crates/wayland/src/state/desktop_icons.rs b/crates/wayland/src/state/desktop_icons.rs index 05bd85a..ad7796f 100644 --- a/crates/wayland/src/state/desktop_icons.rs +++ b/crates/wayland/src/state/desktop_icons.rs @@ -564,12 +564,23 @@ impl CompState { /// against this menu next to Windows'/macOS' own. Same collision- /// avoidance and refresh as `new_desktop_folder` just above. pub(crate) fn new_desktop_text_file(&mut self) { + self.new_desktop_file("New Text Document", "txt"); + } + + /// Creates an empty `~/Desktop/New <stem>.<extension>`, adding ` (2)`, + /// ` (3)` ... until the name is free, then refreshes the icon grid. + /// + /// The de-duplication counter goes before the extension, not after the + /// whole filename: `New Shell Script (2).sh` is still a shell script, + /// `New Shell Script.sh (2)` is not - and the extension is the entire + /// point of letting the type be chosen here. + pub(crate) fn new_desktop_file(&mut self, stem: &str, extension: &str) { let Ok(home) = std::env::var("HOME") else { return }; let desktop = std::path::PathBuf::from(home).join("Desktop"); - let mut name = "New Text Document.txt".to_string(); + let mut name = format!("{stem}.{extension}"); let mut n = 2; while desktop.join(&name).exists() { - name = format!("New Text Document ({n}).txt"); + name = format!("{stem} ({n}).{extension}"); n += 1; } if let Err(e) = std::fs::write(desktop.join(&name), "") { @@ -639,7 +650,15 @@ impl CompState { DesktopMenuAction::OpenTerminalHere => self.open_terminal_here(), DesktopMenuAction::OpenInFileManager => self.open_desktop_in_file_manager(), DesktopMenuAction::SelectAll => self.select_all_desktop_icons(), - DesktopMenuAction::Refresh => self.refresh_desktop_icons(), + DesktopMenuAction::NewFileOfType { label, extension } => self.new_desktop_file(&format!("New {label}"), extension), + DesktopMenuAction::Refresh => { + self.refresh_desktop_icons(); + // Re-reads the Lua config and fires `srd.on("refresh")`, + // so a user's own "restart my bar / reload my shell" list + // runs from the same menu row - see + // `WindowManager::request_refresh`. + self.wm.borrow_mut().request_refresh(); + } // Never actually reached - the click-dispatch site intercepts // `Separator` first, same as `context_menu::MenuAction:: // Separator`'s own dispatch. Handled here too so this match diff --git a/crates/wayland/src/state/lifecycle.rs b/crates/wayland/src/state/lifecycle.rs index 51297bb..92f7d9d 100644 --- a/crates/wayland/src/state/lifecycle.rs +++ b/crates/wayland/src/state/lifecycle.rs @@ -108,9 +108,37 @@ impl CompState { let is_dialog = self.id_to_window.get(&id).is_some_and(|dw| { dw.toplevel().is_some_and(|t| t.parent().is_some()) || dw.x11_surface().is_some_and(|x| x.is_transient_for().is_some()) }); + // Resolved the same way and at the same time as `is_dialog` above, + // for the same reason: this is protocol state, which `core` cannot + // read for itself. A client that pinned min == max on both axes is + // telling us it cannot be resized - and therefore cannot be + // maximized, so the Maximize button would do nothing. `0` means + // "unconstrained" on that axis in both protocols, so a zero on + // either side is never a match. See `Window::resizable`. + let resizable = self.id_to_window.get(&id).is_none_or(|dw| { + if let Some(toplevel) = dw.toplevel() { + let (min, max) = smithay::wayland::compositor::with_states(toplevel.wl_surface(), |states| { + let mut cached = states.cached_state.get::<smithay::wayland::shell::xdg::SurfaceCachedState>(); + let current = cached.current(); + (current.min_size, current.max_size) + }); + return !(min.w > 0 && min.h > 0 && min == max); + } + if let Some(x11) = dw.x11_surface() { + if let (Some(min), Some(max)) = (x11.min_size(), x11.max_size()) { + return min != max; + } + } + true + }); if let Some(win) = self.wm.borrow_mut().window_mut(id) { win.is_dialog = is_dialog; + win.resizable = resizable; } + let show_maximize = { + let wm = self.wm.borrow(); + wm.window(id).is_none_or(|win| wm.show_maximize(win)) + }; // Corrects `w.geometry`'s far edge to match what the client's // surface really committed, when that's known - see // `effective_frame`'s own doc comment. Every bitmap this method @@ -206,6 +234,7 @@ impl CompState { theme.button_order, theme.traffic_light_buttons, is_dialog, + show_maximize, ); let buffer = MemoryRenderBuffer::from_slice(&data, Fourcc::Argb8888, (width as i32, TITLEBAR_HEIGHT as i32), 1, Transform::Normal, None); self.decorations.insert(id, buffer); diff --git a/crates/wayland/src/state/menu.rs b/crates/wayland/src/state/menu.rs index 82e4434..d5e91cb 100644 --- a/crates/wayland/src/state/menu.rs +++ b/crates/wayland/src/state/menu.rs @@ -143,9 +143,21 @@ impl CompState { /// (global space, by convention the maximize button's own titlebar /// position). Same build-once-on-open pattern as `open_context_menu`. pub(crate) fn open_snap_flyout(&mut self, window: WindowId, pos: (i32, i32)) { - let flyout = crate::snap_flyout::SnapFlyout::open(window, pos); + let mut flyout = crate::snap_flyout::SnapFlyout::open(window, pos); let theme = self.wm.borrow().theme; let labels: Vec<&str> = flyout.cells().iter().map(|z| z.label()).collect(); + // Grow each cell to fit the widest label, exactly as + // `open_context_menu` above already does for menu rows - never + // shrinking below the built-in minimum `SnapFlyout::open` picked. + // + // The flyout was the one labelled surface in this compositor that + // never got this treatment: at the fixed 90px cell width "Bottom + // Right" was cut off mid-word and "Top Left" ran into its + // neighbour, the same "text goes out of view" fault already + // reported and fixed for the context menu. + let font = decoration::find_system_font(); + let widest = labels.iter().map(|l| decoration::measure_text_width(&font, l, decoration::FONT_PIXELS)).fold(0.0_f32, f32::max); + flyout.cell_width = flyout.cell_width.max((widest + decoration::TEXT_LEFT_PADDING * 2.0).ceil() as u32); let data = decoration::render_snap_flyout(3, flyout.cell_width, flyout.cell_height, &labels, theme.titlebar_bg, theme.titlebar_fg_focused, theme.default_border_color); let buffer = MemoryRenderBuffer::from_slice(&data, Fourcc::Argb8888, (flyout.width() as i32, flyout.height() as i32), 1, Transform::Normal, None); self.snap_flyout_buffer = Some(buffer); diff --git a/crates/wayland/src/state/mod.rs b/crates/wayland/src/state/mod.rs index 6396692..fee7af3 100644 --- a/crates/wayland/src/state/mod.rs +++ b/crates/wayland/src/state/mod.rs @@ -582,6 +582,13 @@ pub(crate) struct CompState { /// dropping them would lose the damage-tracking stability the whole /// scheme exists for the moment fragment counts fluctuate back up. pub(crate) border_side_buffers: HashMap<WindowId, Vec<SolidColorBuffer>>, + /// Persistent solid-colour buffers backing the drag snap-preview + /// overlay (fill plus four outline strips) - same "reuse the buffer so + /// its `Id` stays stable across frames" reasoning as + /// `border_side_buffers` above. One pool, not one per window: only ever + /// one drag is in progress at a time. See + /// `elements::snap_preview_elements`. + pub(crate) snap_preview_buffers: Vec<SolidColorBuffer>, /// Persistent solid-colour buffer backing the whole-output night-light/ /// reading-mode overlay, one per output name - same "reuse the buffer /// so its `Id` stays stable across frames" reasoning as `border_side_ |