Fix modal keyboard navigation and title switches; refresh documentation

This commit is contained in:
Samuel Onoja committed 2026-10-01 07:38:43 +01:00
1 parent 1b665f4157
commit 67d2d95a01
9 files changed
+312 -192

No files matched your search

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