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
+299 -179

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)
[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.
+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 {
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)),
}
}
+95 -98
View File
@@ -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.
+11 -9
View File
@@ -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.
+14 -1
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!(
&event,
Event::Keyboard(cosmic::iced::keyboard::Event::KeyPressed {
@@ -1841,8 +1851,11 @@ impl FereseShell {
}
}
Task::batch([
self.focus_system_modal(id),
window::run(id, native_wayland_surface)
.map(move |surface| cosmic::Action::App(Message::NativeSurface(id, 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),
+26 -12
View File
@@ -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<Message>> {
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)
+85 -2
View File
@@ -67,6 +67,7 @@ pub(super) struct SystemModal {
pub(super) motion: motion::PopupMotion,
error: Option<String>,
inhibitors: Option<compositor_ipc::Approval>,
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<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> {
let Some(modal) = &mut self.system_modal else {
return Task::none();
@@ -440,9 +462,11 @@ impl FereseShell {
rows = rows.push(text(error).size(13));
}
rows.push(
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
@@ -453,6 +477,8 @@ impl FereseShell {
]
.spacing(10),
)
.id("ferese-system-modal-controls"),
)
}
Content::Guide(entries) => {
let mut bindings = column::with_capacity(entries.len()).spacing(12);
@@ -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<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)]
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<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]
fn only_power_actions_require_confirmation() {
assert_eq!(
+14 -12
View File
@@ -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).
+18 -13
View File
@@ -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.