srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/state
diff options
context:
space:
mode:
Diffstat (limited to 'crates/wayland/src/state')
-rw-r--r--crates/wayland/src/state/desktop_icons.rs25
-rw-r--r--crates/wayland/src/state/lifecycle.rs29
-rw-r--r--crates/wayland/src/state/menu.rs14
-rw-r--r--crates/wayland/src/state/mod.rs7
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_