diff --git a/README.md b/README.md index ffa7629..0817dff 100644 --- a/README.md +++ b/README.md @@ -15,22 +15,21 @@ Ferese is a window manager and desktop shell built together and configured in on ![Ferese Blue desktop with Settings and Control Center](docs/images/screenshots/ferese-main-hero.png) -[View more screenshots](docs/screenshots.md). +[View more screenshots](docs/screenshots.md) ## Features -- Scrolling and tree layouts, floating windows, and a workspace overview. -- Configurable keyboard shortcuts and touchpad gestures. -- Multiple monitors with fractional scaling. -- Six theme families, Light/Dark/Auto modes, custom KDL themes, wallpapers, blur, and adjustable corners. -- Clock and sticky-note widgets. -- Screenshots with Satty editing, screen sharing, and top-bar recording. - +- Arrange your windows in scrolling columns or tree layouts, keep some floating, and bring your workspaces together in overview +- Move around with keyboard shortcuts and touchpad gestures you can change to suit you +- Set up multiple monitors with fractional scaling +- Make the whole desktop your own with six theme families, Light/Dark/Auto modes, custom KDL themes, wallpapers, blur and adjustable corners +- Keep a clock and sticky notes on your desktop +- Capture and edit screenshots with Satty, share a window or display, and record from the top bar ## Install -Install the [dependencies](docs/installation.md#requirements), then build as your -normal user: +Start with the [dependencies](docs/installation.md#requirements), then clone Ferese and +build it as your normal user: ```sh git clone https://github.com/ferese-wm/ferese.git @@ -38,22 +37,24 @@ cd ferese ./scripts/install.sh ``` -Log out, select **Ferese** at your login screen, and sign in. For a TTY or a -greeter that accepts a command, run `ferese-session`. -See [Installation](docs/installation.md) for previews, updates, and recovery. +Once it’s installed, log out and choose **Ferese** at your login screen, or run +`ferese-session` from a TTY or a greeter that accepts a command. The [installation +guide](docs/installation.md) also walks you through previews, updates and recovery. ## Configure -Open **Control Center → Settings**, or edit `~/.config/ferese/config.kdl`. -Changes reload automatically; invalid edits keep the last working configuration. +Make it yours through **Control Center → Settings** or `~/.config/ferese/config.kdl`, +with changes applying as you make them and the last working configuration staying in +place if an edit is invalid. -See the [configuration reference](docs/configuration.md) and -[example config](packaging/config.kdl). +The [configuration reference](docs/configuration.md) and [example +config](packaging/config.kdl) cover everything from appearance and window layouts to +displays and startup apps. ## Default shortcuts -`Super` is usually the Windows key. Change shortcuts and gestures in -**Settings → Shortcuts**. +Use `Super`, usually the Windows key, to move around your desktop, and open **Settings → +Shortcuts** whenever you want to change a shortcut or gesture. | Control | Action | | --- | --- | @@ -72,13 +73,16 @@ See the [configuration reference](docs/configuration.md) and | Three-finger swipe up / down | Next / previous workspace | | Three-finger swipe left / right | Focus the window to the right / left | -See [all shortcuts and mouse actions](docs/shortcuts.md), including Super-drag -to move and Super-right-drag to resize floating windows. +Explore [all shortcuts and mouse actions](docs/shortcuts.md) for more ways to arrange +your windows and move between workspaces. ## Documentation -Start with the [documentation index](docs/README.md) for user guides and -[development instructions](docs/development.md) for builds and testing. +The [documentation index](docs/README.md) brings the desktop guides together, while +[development instructions](docs/development.md) cover building, testing and +contributing. -Ferese is written in Rust using Smithay. Report bugs on -[GitHub](https://github.com/ferese-wm/ferese/issues) with reproduction steps and logs. +Ferese is written in Rust using Smithay, with development happening [on +GitHub](https://github.com/ferese-wm/ferese). If something goes wrong, [open an +issue](https://github.com/ferese-wm/ferese/issues) with the steps to reproduce it and +any relevant logs. diff --git a/crates/ferese-theme/src/controls.rs b/crates/ferese-theme/src/controls.rs index c52f337..24ec8f0 100644 --- a/crates/ferese-theme/src/controls.rs +++ b/crates/ferese-theme/src/controls.rs @@ -92,7 +92,7 @@ pub fn settings_input(p: Palette) -> theme::TextInput { } fn styled_button(p: Palette, selected: bool, navigation: bool) -> theme::Button { - let style = move |hover: bool| { + let style = move |hover: bool, focused: bool| { let background = if selected { mix(p.sidebar, p.accent, if hover { 0.24 } else { 0.17 }) } else if hover { @@ -115,18 +115,18 @@ fn styled_button(p: Palette, selected: bool, navigation: bool) -> theme::Button border_radius: p.radius.min(9.).into(), border_width: if selected { 1. } else { 0. }, border_color: if selected { p.accent } else { Color::TRANSPARENT }, - outline_width: 0., - outline_color: Color::TRANSPARENT, + outline_width: if focused { 2. } else { 0. }, + outline_color: p.accent, overlay: None, shadow_offset: Vector::ZERO, } }; theme::Button::Custom { - active: Box::new(move |_, _| style(false)), - hovered: Box::new(move |_, _| style(true)), - pressed: Box::new(move |_, _| style(true)), - disabled: Box::new(move |_| style(false)), + active: Box::new(move |focused, _| style(false, focused)), + hovered: Box::new(move |focused, _| style(true, focused)), + pressed: Box::new(move |focused, _| style(true, focused)), + disabled: Box::new(move |_| style(false, false)), } } diff --git a/docs/installation.md b/docs/installation.md index 3d8bffe..34c837a 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,17 +1,17 @@ # Installation -Ferese runs on Linux and installs as a standalone Wayland session. Use any login -manager that can launch Wayland sessions, a command-based greeter, or a local -TTY. You do not need to replace your existing login manager or desktop. +Ferese runs on Linux as its own Wayland session, alongside your existing desktop, and +you can launch it from a login manager, a command-based greeter or a local TTY. ## Requirements -Use a Wayland-capable graphics driver and a local user session with seat access -through logind or seatd. A nested preview runs inside an existing Wayland desktop. +For a full session, you’ll need a Wayland-capable graphics driver and a local user +session with seat access through logind or seatd, while a nested preview runs inside the +Wayland desktop you already use. -Building requires **Rust 1.95 or newer**, a C/C++ toolchain, CMake, pkg-config, -and the libraries below. The installer builds all Ferese components; install -system packages separately. +To build Ferese, install **Rust 1.95 or newer**, a C/C++ toolchain, CMake, pkg-config +and the libraries below. The installer builds the Ferese components once those system +packages are in place. ### Fedora @@ -49,9 +49,10 @@ sudo apt install build-essential git curl cmake pkg-config libwayland-dev \ gstreamer1.0-plugins-good ``` -Fedora is the tested installation path. Package names and versions may differ -on other distributions. See [Smithay dependencies](https://github.com/Smithay/smithay#system-dependencies) -for equivalent libraries. +Fedora is the tested installation path, and package names or versions may differ on +other distributions, where the [Smithay +dependencies](https://github.com/Smithay/smithay#system-dependencies) can help you find +equivalent libraries. ### Rust toolchain @@ -62,9 +63,9 @@ rustc --version cargo --version ``` -If Rust is missing, install it using the instructions at -[rustup.rs](https://rustup.rs/), then reopen your terminal. With rustup installed, -you can select the required version for this checkout after cloning: +If you don’t have Rust yet, follow the instructions at [rustup.rs](https://rustup.rs/) +and reopen your terminal, then select the required toolchain for this checkout after +cloning: ```sh rustup toolchain install 1.95.0 @@ -81,21 +82,22 @@ cd ferese ./scripts/install.sh ``` -The script builds locked release binaries, then requests administrator access -through `sudo` or `pkexec` for installation. Do not run the whole build as root. -The first build needs network access to fetch Rust dependencies. +The script builds locked release binaries as your normal user and then asks for +administrator access through `sudo` or `pkexec` to install them, so leave the build +itself unprivileged. The first build needs network access to fetch Rust dependencies. -The installer adds the session launcher, desktop tools, portal backend, login -entry, icons, default wallpaper, and example config. Releases live under -`/usr/local/lib/ferese/releases/`; commands live under `/usr/local/bin/`. -The `current` and `previous` links support updates and rollback. +Installation brings together the session launcher, desktop tools, portal backend, login +entry, icons, default wallpaper and example config. Releases live under +`/usr/local/lib/ferese/releases/` and commands under `/usr/local/bin/`, with `current` +and `previous` links for switching between releases during updates and rollback. -Your configuration, other desktop sessions, and existing PAM policy are preserved. -Ferese starts its authentication agent and portal services with the session. +Your configuration, other desktop sessions and existing PAM policy stay in place, while +Ferese starts its authentication agent and portal services together with the session. ### Initial configuration -Ferese has built-in defaults. To start from the example without replacing your config: +You can start with Ferese’s built-in defaults, or copy the example config if you’d like +a file to customize: ```sh mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/ferese" @@ -105,18 +107,18 @@ if [ ! -e "${XDG_CONFIG_HOME:-$HOME/.config}/ferese/config.kdl" ]; then fi ``` -Open **Control Center → Settings** after login, or run `ferese-settings`. -See [Configuration](configuration.md) for themes, displays, shortcuts, and startup -applications. The default terminal shortcut uses `foot`; change the terminal -command in Settings if you prefer another terminal. +After login, open **Control Center → Settings** or run `ferese-settings` to shape your +desktop, using [Configuration](configuration.md) for themes, displays, shortcuts and +startup apps. The default terminal is `foot`, which you can replace with your preferred +terminal command in Settings. ## Start a session ### Graphical login managers -Save your work and log out. Open your login screen's session selector, choose -**Ferese**, and sign in. Any manager that supports Wayland sessions and reads -`/usr/share/wayland-sessions/` can discover the installed entry. +Save your work and log out, then open your login screen’s session selector, choose +**Ferese** and sign in. Login managers that support Wayland sessions can discover the +installed entry in `/usr/share/wayland-sessions/`. If Ferese is missing, confirm that the session entry exists and its launcher is executable: @@ -127,9 +129,9 @@ test -x /usr/local/bin/ferese-session && echo "Session launcher is ready" desktop-file-validate /usr/share/wayland-sessions/ferese.desktop ``` -Check your manager's documentation for enabling Wayland session discovery. A -manager that only launches X11 sessions cannot start Ferese through that path; -use a Wayland-capable greeter or the TTY method below. +If the entry still doesn’t appear, check your login manager’s documentation for Wayland +session discovery, or use a Wayland-capable greeter or the TTY method below if your +manager only launches X11 sessions. ### Command-based greeters @@ -139,26 +141,24 @@ If your greeter asks for a session command instead of reading desktop files, use /usr/local/bin/ferese-session ``` -For greetd, use this as the session command after authentication; see the -[greetd documentation](https://sr.ht/~kennylevinsen/greetd/). -The launcher starts the compositor, shell, and session services together. +For greetd, set this as the session command after authentication using the [greetd +documentation](https://sr.ht/~kennylevinsen/greetd/), and the launcher will start the +compositor, shell and session services together. ### From a TTY -Log out of the graphical session, switch to a local console with -**Ctrl+Alt+F2** (or another available function key), and sign in as your normal -user. Then run: +Log out of your graphical session and switch to a local console with **Ctrl+Alt+F2**, or +another available function key, then sign in as your normal user and run: ```sh /usr/local/bin/ferese-session ``` -Your login must provide `XDG_RUNTIME_DIR` and access to the active seat through -logind or seatd. If either is missing, fix the distribution's login/session setup; -do not work around it by running Ferese as root or making device nodes writable. -For non-systemd systems, follow your distribution's seatd and user-session setup. -Do not start the DRM backend inside an existing graphical session; use a nested -preview there. +Your login needs to provide `XDG_RUNTIME_DIR` and access to the active seat through +logind or seatd; if either is missing, fix your distribution’s login and session setup +rather than running Ferese as root or making device nodes writable. On non-systemd +systems, follow the distribution’s seatd and user-session instructions, and use a nested +preview whenever you’re already inside a graphical session. ## Optional desktop tools @@ -173,18 +173,18 @@ Install these through your distribution when you want the corresponding features | Audio controls | PipeWire, WirePlumber, and `wpctl` | | Bluetooth controls | BlueZ, `bluetoothctl`, and its running service | -Package names and availability vary by distribution. In particular, install -Satty using its [upstream instructions](https://github.com/Satty-org/Satty#install) -if your repositories do not provide it. Ferese does not enable these services -or replace your existing service configuration during installation. +Package names and availability vary by distribution, so use Satty’s [upstream +instructions](https://github.com/Satty-org/Satty#install) if it isn’t in your +repositories. Ferese leaves service setup to you and preserves your existing service +configuration during installation. -Print Screen captures the active monitor; **Super+Shift+S** selects an area. -Use `ferese-screenshot --all` to capture all monitors in one image. +Use **Print Screen** to capture the active monitor, **Super+Shift+S** to select an area, +or `ferese-screenshot --all` to capture all monitors in one image. -The native locker follows your shell theme. Open **Settings → Lock Screen** to -customize it, or run `ferese-lock --preview` to see an ordinary preview window. -Test real unlocking before enabling automatic locking; see -[Native locker](locking.md) for authentication, idle locking, and limitations. +The native locker follows your shell theme and can be personalized in **Settings → Lock +Screen**, with `ferese-lock --preview` opening an ordinary preview window. Before +enabling automatic locking, test real unlocking and read [Native locker](locking.md) for +authentication, idle locking and limits. ## Preview and logs @@ -204,21 +204,19 @@ After installation, the equivalent launcher is: ferese-session --nested ``` -The session launcher stores logs in `${XDG_STATE_HOME:-$HOME/.local/state}/ferese/`. -A directly launched source build writes to its terminal. Keep another desktop -available while testing scaling, monitor hotplug, suspend, and locking. +The session launcher saves logs in `${XDG_STATE_HOME:-$HOME/.local/state}/ferese/`, +while a source build launched directly writes to its terminal. Keep another desktop +available when testing scaling, monitor hotplug, suspend or locking so you have a way +back if something goes wrong. -The launcher enables the screencopy protocol for screenshot tools. Set -`FERESE_ENABLE_SCREENCOPY=0` in the session environment to disable it. This -turns off every path that can read the screen, including the built-in -screenshot command, not only the Wayland protocol. When enabled, Wayland clients -can capture the unlocked desktop, and `feresectl screenshot` is available. -Capture remains blocked while the session is locked. Launcher changes take -effect at next login. +The launcher enables screencopy for screenshot tools, allowing Wayland clients to +capture the unlocked desktop and making `feresectl screenshot` available. Set +`FERESE_ENABLE_SCREENCOPY=0` in the session environment to disable every screen-reading +path, including the built-in screenshot command, at your next login; capture is always +blocked while the session is locked. -Starting the compositor directly instead of through `ferese-session` leaves -`FERESE_ENABLE_SCREENCOPY` unset, which disables screenshots. Export -`FERESE_ENABLE_SCREENCOPY=1` to use them. +When you launch the compositor directly, set `FERESE_ENABLE_SCREENCOPY=1` to enable +screenshots, since that variable is otherwise unset and screen capture stays disabled. ## Updating @@ -229,14 +227,14 @@ git pull --ff-only ./scripts/install.sh ``` -Log out and back in to use the new release. Running sessions keep their existing -processes and release paths until restarted. +Log out and back in to start using the new release, as a running session keeps its +existing processes and release paths until it restarts. -The installer records shipped portal configuration with each release for future -upgrades. Modified or untracked administrator files stop installation. To replace -Ferese portal configuration from an earlier development install, use -`./scripts/install.sh --skip-build --replace-portal-config` after building. Existing -files are backed up under the new release's `portal-config.previous/` directory. +Each release records the portal configuration it ships so later upgrades can identify +it, and installation stops if it encounters modified or untracked administrator files. +To replace Ferese portal configuration from an earlier development install, build first +and run `./scripts/install.sh --skip-build --replace-portal-config`, which backs up the +existing files in the new release’s `portal-config.previous/` directory. Installer options: @@ -246,25 +244,25 @@ Installer options: ./scripts/install.sh --skip-build ``` -`--dry-run` prints the planned commands. `--offline` requires dependencies to be -cached already. `--skip-build` installs the binaries already in `target/release/`; -use it only after building all components from the intended revision. -Run `./scripts/install.sh --help` for all options. +Use `--dry-run` to see the planned commands, `--offline` when dependencies are already +cached, or `--skip-build` after building every component from the revision you want to +install. The latter uses binaries already in `target/release/`, and +`./scripts/install.sh --help` lists all available options. ## Logout and recovery -Use **Super+Shift+E** or `feresectl request-logout` to ask for logout confirmation. -`feresectl exit` ends the session immediately; save your work first. -If Ferese freezes, switch to another TTY with **Ctrl+Alt+F1–F12**, sign in, and -identify the affected compositor process: +Save your work before leaving: **Super+Shift+E** and `feresectl request-logout` ask for +confirmation, while `feresectl exit` ends the session immediately. If Ferese freezes, +switch to another TTY with **Ctrl+Alt+F1–F12**, sign in and find the affected compositor +process: ```sh pgrep -a -u "$USER" -x ferese ``` -Terminate only that session's process with `kill PID`, replacing `PID` with the -correct process ID. This ends its applications and can lose unsaved work. You -can then select your other desktop at the next login. +Replace `PID` with the affected session’s process ID and use `kill PID` to end it, +bearing in mind that its applications will close and unsaved work may be lost. You can +then choose another desktop at the next login. ### Roll back a release @@ -288,9 +286,9 @@ sudo sh -eu -c ' ' ``` -Then log in again. This changes the selected binaries; it does not restore your -config or replace the PAM policy. Settings keeps a backup when saving config -changes; see [Saving and validation](configuration.md#saving-and-validation). +Log in again to use the selected binaries, with your config and PAM policy still in +place. If you also need to recover a configuration edit, Settings keeps a backup when +saving, as described in [Saving and validation](configuration.md#saving-and-validation). ### Remove the login option @@ -301,14 +299,13 @@ sudo mv /usr/share/wayland-sessions/ferese.desktop \ /usr/local/lib/ferese/ferese.desktop.disabled ``` -For a command-based greeter, remove Ferese from its session choices using that -greeter's configuration. Reinstalling restores the desktop session entry. +For a command-based greeter, remove Ferese from its session choices in the greeter’s +configuration; reinstalling Ferese restores the desktop session entry. ## Screen sharing and recording -The installer includes Ferese’s native ScreenCast portal. It backs up the old -user service override that points to `~/.local/libexec/ferese`, so new sessions -use the installed release. Custom overrides are preserved and reported before -building. See -[Screen sharing](screen-sharing.md) for consent, PipeWire setup, supported sources, -and current recording limitations. +Ferese’s native ScreenCast portal is included in the installation, which backs up the +old user service override pointing to `~/.local/libexec/ferese` so new sessions use the +installed release. Custom overrides are preserved and reported before building, and +[Screen sharing](screen-sharing.md) covers consent, PipeWire setup, supported sources +and recording limits. diff --git a/docs/why-ferese.md b/docs/why-ferese.md index c197162..fa68357 100644 --- a/docs/why-ferese.md +++ b/docs/why-ferese.md @@ -4,21 +4,22 @@ Most Wayland desktops are assembled from separate programs such as a compositor, ## Choose how windows fit -Scrolling columns let you keep windows at useful widths and move through them -horizontally. Tree tiling divides the screen into nested splits. Floating windows -work alongside either layout, and overview shows windows across workspaces. +Keep windows at useful widths and move through them horizontally with scrolling columns, +divide the screen into nested splits with tree tiling, or let individual windows float +alongside either layout. When you want to see how everything fits together, overview +brings your windows and workspaces into view. ![Ferese Overview showing windows and workspaces](images/screenshots/ferese-blue-overview.png) ## Change the desktop together -Choose a theme in Settings, then adjust the wallpaper, colors, font, corners, -and motion. Changes apply live. Window corners and shell corners can be set -independently. +Choose a theme in Settings and build on it with your own wallpaper, colors, font, +corners and motion, with changes applying across the desktop as you make them. Window +and shell corners have separate controls, so you can shape each to your liking. ![Ferese Settings and Control Center in Rosé Pine](images/screenshots/rose-pine-desktop.png) -For example, this changes shell corners: +To give your shell corners a different radius, add: ```kdl theme { @@ -28,5 +29,6 @@ theme { } ``` -See [Configuration](configuration.md) for all options, or -[Installation](installation.md) to try Ferese in a nested preview. +Explore [Configuration](configuration.md) for everything you can change, or follow +[Installation](installation.md) to try Ferese inside your current desktop with a nested +preview. diff --git a/shell/ferese-shell/src/main.rs b/shell/ferese-shell/src/main.rs index 54f09f4..f9fb1a0 100644 --- a/shell/ferese-shell/src/main.rs +++ b/shell/ferese-shell/src/main.rs @@ -1794,6 +1794,16 @@ impl FereseShell { _ => {} } } + if let Event::Keyboard(cosmic::iced::keyboard::Event::KeyPressed { + key: cosmic::iced::keyboard::Key::Named(cosmic::iced::keyboard::key::Named::Tab), + modifiers, + .. + }) = &event + && !modifiers.control() + && self.system_modal.as_ref().is_some_and(|modal| modal.contains(id)) + { + return self.navigate_system_modal(modifiers.shift()); + } if matches!( &event, Event::Keyboard(cosmic::iced::keyboard::Event::KeyPressed { @@ -1841,8 +1851,11 @@ impl FereseShell { } } - window::run(id, native_wayland_surface) - .map(move |surface| cosmic::Action::App(Message::NativeSurface(id, surface))) + Task::batch([ + self.focus_system_modal(id), + window::run(id, native_wayland_surface) + .map(move |surface| cosmic::Action::App(Message::NativeSurface(id, surface))), + ]) } Event::Keyboard(cosmic::iced::keyboard::Event::KeyPressed { key: cosmic::iced::keyboard::Key::Named(cosmic::iced::keyboard::key::Named::Escape), diff --git a/shell/ferese-shell/src/status_ui.rs b/shell/ferese-shell/src/status_ui.rs index 6820c79..d1282ea 100644 --- a/shell/ferese-shell/src/status_ui.rs +++ b/shell/ferese-shell/src/status_ui.rs @@ -435,6 +435,22 @@ impl FereseShell { p, )); } + if kind == Menu::Network + && let Some(network) = &self.status.network + { + heading = heading.push(shell_switch( + "Wi-Fi", + network.enabled, + Action::Wifi(!network.enabled), + theme, + p, + )); + } + if kind == Menu::Audio + && let Some(audio) = &self.status.audio + { + heading = heading.push(shell_switch("Mute", audio.muted, Action::Mute(!audio.muted), theme, p)); + } let mut rows = column::with_capacity(12).spacing(12).width(Length::Fill); if kind != Menu::Calendar { rows = rows.push(heading); @@ -546,7 +562,8 @@ impl FereseShell { muted, color_with_opacity(theme.accent, p), p, - Some((n.enabled, Action::Wifi(!n.enabled))), + n.enabled, + None, )); if connected { let strength = match n.signal { @@ -589,6 +606,7 @@ impl FereseShell { muted, color_with_opacity(theme.accent, p), p, + b.enabled, Some((b.enabled, Action::Bluetooth(!b.enabled))), )); @@ -620,17 +638,13 @@ impl FereseShell { if (combined || menu.kind == Menu::Audio) && let Some(a) = &self.status.audio { + let mut volume_heading = row![text("Volume").size(13).width(Length::Fill)].align_y(Alignment::Center); + if combined { + volume_heading = + volume_heading.push(shell_switch("Mute", a.muted, Action::Mute(!a.muted), theme, p)); + } let audio = column![ - row![ - text("Volume").size(13).width(Length::Fill), - menu_button( - if a.muted { "Unmute" } else { "Mute" }, - Message::Control(Action::Mute(!a.muted)), - primary, - p - ) - ] - .align_y(Alignment::Center), + volume_heading, text(&a.output).size(12).class(theme::Text::Color(muted)), slider_row( audio_icon(a.volume, a.muted), @@ -856,9 +870,9 @@ fn status_summary<'a>( muted: Color, accent: Color, opacity: f32, + enabled: bool, toggle: Option<(bool, Action)>, ) -> Element<'a, cosmic::Action> { - let enabled = toggle.as_ref().is_some_and(|(enabled, _)| *enabled); let badge_color = if enabled { accent } else { primary }; let badge = container(accented_icon(source, 22, primary, accent)) .width(32) diff --git a/shell/ferese-shell/src/system_modal.rs b/shell/ferese-shell/src/system_modal.rs index 37d4fa5..74df97c 100644 --- a/shell/ferese-shell/src/system_modal.rs +++ b/shell/ferese-shell/src/system_modal.rs @@ -67,6 +67,7 @@ pub(super) struct SystemModal { pub(super) motion: motion::PopupMotion, error: Option, inhibitors: Option, + keyboard_nav: bool, } impl SystemModal { @@ -190,12 +191,15 @@ impl FereseShell { move |result| cosmic::Action::App(Message::SystemInhibitors(id, result)), )); } + let keyboard_nav = self.core.keyboard_nav(); + self.core.set_keyboard_nav(false); self.system_modal = Some(SystemModal { content, surfaces, motion: motion::PopupMotion::new(self.config.animations), error: None, inhibitors: mode.is_none().then(compositor_ipc::Approval::default), + keyboard_nav, }); Task::batch(tasks) } @@ -293,6 +297,7 @@ impl FereseShell { let Some(modal) = self.system_modal.take() else { return Task::none(); }; + self.core.set_keyboard_nav(modal.keyboard_nav); if cancel && let Content::Power(PowerAction::Logout(serial)) = modal.content && let Some(control) = &self.control @@ -322,6 +327,23 @@ impl FereseShell { } } + pub(super) fn focus_system_modal(&self, id: window::Id) -> Task { + if self.system_modal.as_ref().is_some_and(|modal| { + !modal.motion.closing() && modal.surfaces.iter().any(|surface| surface.id == id && surface.primary) + }) { + button::focus("ferese-modal-cancel".into()) + } else { + Task::none() + } + } + + pub(super) fn navigate_system_modal(&self, backwards: bool) -> Task { + if self.system_modal.as_ref().is_none_or(|modal| modal.motion.closing()) { + return Task::none(); + } + cosmic::iced::advanced::widget::operate(modal_focus_operation(backwards)) + } + pub(super) fn execute_system_modal(&mut self) -> Task { let Some(modal) = &mut self.system_modal else { return Task::none(); @@ -440,18 +462,22 @@ impl FereseShell { rows = rows.push(text(error).size(13)); } rows.push( - row![ - Space::new().width(Length::Fill), - ferese_theme::controls::text_button("Cancel", shell_font(), palette, false) - .on_press(cosmic::Action::App(Message::CancelPower)), - ferese_theme::controls::text_button(label, shell_font(), palette, true).on_press_maybe( - modal - .inhibitors - .is_some() - .then_some(cosmic::Action::App(Message::ExecutePower)) - ), - ] - .spacing(10), + container( + row![ + Space::new().width(Length::Fill), + ferese_theme::controls::text_button("Cancel", shell_font(), palette, false) + .id("ferese-modal-cancel".into()) + .on_press(cosmic::Action::App(Message::CancelPower)), + ferese_theme::controls::text_button(label, shell_font(), palette, true).on_press_maybe( + modal + .inhibitors + .is_some() + .then_some(cosmic::Action::App(Message::ExecutePower)) + ), + ] + .spacing(10), + ) + .id("ferese-system-modal-controls"), ) } Content::Guide(entries) => { @@ -482,11 +508,13 @@ impl FereseShell { text("Disable this guide in Settings → Shortcuts. It appears at each login until disabled.") .size(12) .class(theme::Text::Color(palette.muted)), - row![ + container(row![ Space::new().width(Length::Fill), ferese_theme::controls::text_button("Got it", shell_font(), palette, true,) + .id("ferese-modal-cancel".into()) .on_press(cosmic::Action::App(Message::CancelPower)), - ], + ]) + .id("ferese-system-modal-controls"), ] .spacing(14) } @@ -537,10 +565,65 @@ impl FereseShell { } } +// Restrict Tab traversal to the modal actions, excluding the bar and desktop widgets. +fn modal_focus_operation(backwards: bool) -> Box> { + use cosmic::iced::advanced::widget::{Id, operation}; + let target = Id::new("ferese-system-modal-controls"); + if backwards { + Box::new(operation::scoped(target, operation::focusable::focus_previous())) + } else { + Box::new(operation::scoped(target, operation::focusable::focus_next())) + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn tab_stays_within_modal_actions_in_both_directions() { + use cosmic::iced::Rectangle; + use cosmic::iced::advanced::widget::{Id, Operation, operation}; + + #[derive(Default)] + struct Focus(bool); + impl operation::Focusable for Focus { + fn is_focused(&self) -> bool { + self.0 + } + fn focus(&mut self) { + self.0 = true; + } + fn unfocus(&mut self) { + self.0 = false; + } + } + + let mut outside = Focus(true); + let mut cancel = Focus(true); + let mut confirm = Focus(false); + for (backwards, expected_cancel) in [(false, false), (false, true), (true, false), (true, true)] { + let mut current: Box> = modal_focus_operation(backwards); + loop { + current.focusable(Some(&Id::new("bar")), Rectangle::default(), &mut outside); + current.container(Some(&Id::new("ferese-system-modal-controls")), Rectangle::default()); + current.traverse(&mut |operation| { + operation.focusable(Some(&Id::new("cancel")), Rectangle::default(), &mut cancel); + operation.focusable(Some(&Id::new("confirm")), Rectangle::default(), &mut confirm); + }); + match current.finish() { + operation::Outcome::Chain(next) => current = next, + _ => break, + } + } + assert!(outside.0, "modal navigation changed focus outside the modal"); + assert_ne!(cancel.0, confirm.0); + assert_eq!(cancel.0, expected_cancel); + } + assert!(cancel.0); + assert!(!confirm.0); + } + #[test] fn only_power_actions_require_confirmation() { assert_eq!( diff --git a/site/README.md b/site/README.md index 0304fcb..59494bc 100644 --- a/site/README.md +++ b/site/README.md @@ -1,7 +1,8 @@ # Ferese website and handbook -The public site is static HTML and CSS. The handbook is generated from the -project's Markdown guides, so website and repository documentation stay in sync. +The website introduces Ferese as one desktop you can make your own, with a handbook +built from the project’s Markdown guides so the website and repository share the same +documentation. ## Local preview @@ -12,8 +13,9 @@ python3 -m venv .site-venv python3 -m http.server 8080 --bind 127.0.0.1 --directory build/site ``` -Open http://localhost:8080 or http://localhost:8080/docs/. Rebuild after editing -site sources or Markdown guides, then refresh the browser. +Open http://localhost:8080 for the homepage or http://localhost:8080/docs/ for the +handbook, then rebuild and refresh the browser whenever you change the site sources or +guides. - `site/index.html` and `site/styles.css`: homepage and shared visual design. - `site/content/index.md`: handbook introduction. @@ -25,13 +27,13 @@ site sources or Markdown guides, then refresh the browser. ## GitHub Pages -Select **Settings → Pages → Source → GitHub Actions**. The Pages workflow -publishes site and documentation changes pushed to `main`, or can run manually. -Expected URL: https://ferese-wm.github.io/ferese/. +Choose **Settings → Pages → Source → GitHub Actions** to publish through the Pages +workflow, which runs when site or documentation changes reach `main` and can also be +started manually. The expected address is https://ferese-wm.github.io/ferese/. -Local links are relative, including documentation search, so publishing under -the `/ferese/` project path works. Screenshots are copied from `docs/images` -during the build; the generated `build/site` directory is ignored by Git. +The site uses relative links, including for handbook search, so it also works under the +`/ferese/` project path. Each build copies screenshots from `docs/images` into +`build/site`, which is ignored by Git. -See [GitHub's custom workflow documentation](https://docs.github.com/en/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages) -for deployment setup. +For deployment setup, follow [GitHub’s custom workflow +documentation](https://docs.github.com/en/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages). diff --git a/site/content/index.md b/site/content/index.md index c8ea377..602e9ef 100644 --- a/site/content/index.md +++ b/site/content/index.md @@ -1,9 +1,11 @@ # Ferese handbook -Ferese is a Linux Wayland desktop with scrolling columns, tree tiling, floating -windows, and a shared theme for its desktop tools. +Ferese is a window manager and desktop shell built together and configured in one place, +for people who want their desktop to be their own. This handbook helps you get settled, +shape the desktop around the way you work and understand what you can change. -[Browse screenshots](screenshots.md) of the desktop, themes and shell controls. +[Browse screenshots](screenshots.md) to see the themes, layouts and desktop controls in +use ## Get started @@ -13,7 +15,8 @@ windows, and a shared theme for its desktop tools. ## Default shortcuts -`Super` is usually the Windows key. All these shortcuts can be changed in Settings. +Use `Super`, usually the Windows key, for the shortcuts below, and make them your own in +**Settings → Shortcuts**. | Control | Action | | --- | --- | @@ -32,8 +35,8 @@ windows, and a shared theme for its desktop tools. | Three-finger swipe left / right | Focus the window to the right / left | | Login shortcut guide | Appears at login; disable in Settings → Shortcuts | -See [all shortcuts and mouse actions](shortcuts.md), including Super-drag to move -and Super-right-drag to resize floating windows. +The [shortcuts and mouse actions guide](shortcuts.md) covers the rest, including how to +group windows, resize them and move between workspaces. ## Desktop guides @@ -43,14 +46,16 @@ and Super-right-drag to resize floating windows. - [Screen sharing and recording](screen-sharing.md): share an app or display, or save a video. - [Desktop portals](portals.md): integration with applications. -Use Settings or edit `~/.config/ferese/config.kdl`. Changes reload automatically; -invalid edits keep the last working configuration. +Personalize the desktop in Settings or edit `~/.config/ferese/config.kdl` directly, with +changes applying live and the last working configuration staying in place if an edit is +invalid. ## Troubleshooting -See [logs and recovery](installation.md#logout-and-recovery). `feresectl exit` -ends the session immediately; `feresectl request-logout` asks for confirmation. +If you need to end a session, `feresectl request-logout` asks for confirmation and +`feresectl exit` exits immediately, so save your work first. The [logs and recovery +guide](installation.md#logout-and-recovery) helps when something stops responding. -Report problems in the [issue tracker](https://github.com/ferese-wm/ferese/issues). -Include reproduction steps, whether Ferese ran as a nested preview or a direct -session, and relevant logs. Contributors can start with [Development](development.md). +You can [report a problem](https://github.com/ferese-wm/ferese/issues) with the steps to +reproduce it, relevant logs and whether you were using a nested preview or a direct +session, or start with [Development](development.md) if you’d like to contribute.