Add dark website and documentation with GitHub Pages deployment

Include the updated desktop screenshot, active-monitor screenshot capture and editor locking, and Settings window drag areas.
This commit is contained in:
Samuel Onoja committed 2026-09-27 10:34:33 +01:00
1 parent e5e2b74a25
commit 0cd48bcc81
15 files changed
+1794 -12

No files matched your search

+43
View File
@@ -0,0 +1,43 @@
name: Deploy website to GitHub Pages
on:
push:
branches: [main]
paths:
- "site/**"
- "docs/**"
- "packaging/icons/ferese.svg"
- "scripts/build-site.py"
- ".github/workflows/pages.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/configure-pages@v5
- name: Build website and handbook
run: |
python3 -m venv .site-venv
.site-venv/bin/pip install -r site/requirements.txt
.site-venv/bin/python scripts/build-site.py
- uses: actions/upload-pages-artifact@v4
with:
path: build/site
deploy:
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v4
+5
View File
@@ -11,3 +11,8 @@ ChatGPT Image*.png
/docs/ferese-window-manager-spec.md /docs/ferese-window-manager-spec.md
/docs/ferese-theme-ui-spec.md /docs/ferese-theme-ui-spec.md
/docs/ferese-shell-design.md /docs/ferese-shell-design.md
# Generated GitHub Pages site
/build/site/
/.site-venv/
__pycache__/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 397 KiB

+17 -3
View File
@@ -2,14 +2,18 @@
## Build and install ## Build and install
On Fedora with SDDM, install a Rust toolchain (1.93 or newer), native build On Fedora with SDDM, install a Rust toolchain (1.95 or newer), native build
dependencies, `desktop-file-utils`, D-Bus and `foot`. Wallpaper browsing also dependencies, `desktop-file-utils`, D-Bus and `foot`. Wallpaper browsing also
needs `zenity`; locking needs swaylock or swaylock-effects. needs `zenity`; locking needs swaylock or swaylock-effects.
Screenshots need `grim`, `slurp`, `satty`, and `wl-clipboard`. Screenshots need `grim`, `slurp`, `satty`, `wl-clipboard`, and Python 3.
Print Screen captures the active monitor; Super+Shift+S selects an area.
Use `ferese-screenshot --all` to capture all monitors in one image.
From the checkout, run as your normal user: Clone the repository, then run the installer as your normal user:
```sh ```sh
git clone https://github.com/borngraced/ferese.git
cd ferese
./scripts/install.sh ./scripts/install.sh
``` ```
@@ -29,6 +33,16 @@ Use `--help` for all options. New configs can be copied from
## Preview and logs ## Preview and logs
To preview a source build before installing, run these commands from the checkout
inside an existing Wayland desktop:
```sh
cargo build --release --locked -p ferese -p ferese-shell -p ferese-settings -p feresectl
target/release/ferese --backend nested --grant-effects --grant-shell-control -- \
target/release/ferese-shell
```
Run `ferese-session --nested` inside an existing Wayland desktop. For a hardware Run `ferese-session --nested` inside an existing Wayland desktop. For a hardware
session, use the login screen or a spare TTY; never run Ferese as root or start session, use the login screen or a spare TTY; never run Ferese as root or start
its DRM backend inside an active graphical session. its DRM backend inside an active graphical session.
+8
View File
@@ -5,6 +5,8 @@ Cards appear beneath the bar on the focused output and follow the selected theme
Click the notification icon in the bar to open history, toggle Do Not Disturb, or Click the notification icon in the bar to open history, toggle Do Not Disturb, or
clear notifications. Escape or the close button closes the history panel. clear notifications. Escape or the close button closes the history panel.
## Send a notification
Notifications use the standard `org.freedesktop.Notifications` D-Bus interface. Notifications use the standard `org.freedesktop.Notifications` D-Bus interface.
Existing apps and `notify-send` work without Ferese-specific commands: Existing apps and `notify-send` work without Ferese-specific commands:
@@ -12,6 +14,8 @@ Existing apps and `notify-send` work without Ferese-specific commands:
notify-send --app-name=Ferese --icon=dialog-information "Hello" "Your desktop notification" notify-send --app-name=Ferese --icon=dialog-information "Hello" "Your desktop notification"
``` ```
## Popups and history
Up to three popup cards are visible at once. History keeps the most recent 100 Up to three popup cards are visible at once. History keeps the most recent 100
notifications in memory and clears when the shell exits. Hovering a popup pauses notifications in memory and clears when the shell exits. Hovering a popup pauses
its timer. Critical notifications bypass Do Not Disturb and do not expire unless its timer. Critical notifications bypass Do Not Disturb and do not expire unless
@@ -19,6 +23,8 @@ the sender supplies a timeout. App action buttons emit the standard action signa
a replacement updates the same notification ID. Transient messages are removed a replacement updates the same notification ID. Transient messages are removed
from history when they close. from history when they close.
## Settings and Do Not Disturb
Settings → Notifications controls popup visibility, the initial Do Not Disturb Settings → Notifications controls popup visibility, the initial Do Not Disturb
state, and the default timeout. The bar's Do Not Disturb toggle is temporary; state, and the default timeout. The bar's Do Not Disturb toggle is temporary;
changing the Settings option saves the preference for future sessions. Apps can changing the Settings option saves the preference for future sessions. Apps can
@@ -35,6 +41,8 @@ notifications {
The timeout must be between 1000 and 30000 milliseconds. Hiding popups still The timeout must be between 1000 and 30000 milliseconds. Hiding popups still
records notifications in history. Appearance follows the Ferese theme. records notifications in history. Appearance follows the Ferese theme.
## Session integration
Only one notification server can own the D-Bus interface. The Ferese session Only one notification server can own the D-Bus interface. The Ferese session
launcher stops the SwayNotificationCenter user service before starting its shell. launcher stops the SwayNotificationCenter user service before starting its shell.
For a manually started shell, stop that service with `systemctl --user stop For a manually started shell, stop that service with `systemctl --user stop
+25 -7
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Capture an area or the whole desktop and open it in Satty for annotation. # Capture an area, active monitor, or all monitors and edit in Satty.
set -euo pipefail set -euo pipefail
umask 077 umask 077
@@ -15,14 +15,15 @@ mode=area
case ${1:-} in case ${1:-} in
'') ;; '') ;;
--full) mode=full; shift ;; --full) mode=full; shift ;;
--all) mode=all; shift ;;
--help|-h) --help|-h)
echo 'Usage: ferese-screenshot [--full]' echo 'Usage: ferese-screenshot [--full | --all]'
echo 'Select an area, or use --full for the whole desktop, then edit in Satty.' echo 'Select an area; --full captures the active monitor; --all captures all monitors.'
exit 0 exit 0
;; ;;
*) fail 'Usage: ferese-screenshot [--full]' ;; *) fail 'Usage: ferese-screenshot [--full | --all]' ;;
esac esac
(($# == 0)) || fail 'Usage: ferese-screenshot [--full]' (($# == 0)) || fail 'Usage: ferese-screenshot [--full | --all]'
for tool in grim satty wl-copy flock; do for tool in grim satty wl-copy flock; do
command -v "$tool" >/dev/null || fail "Required command not found: $tool" command -v "$tool" >/dev/null || fail "Required command not found: $tool"
@@ -38,6 +39,22 @@ if [[ $mode == area ]]; then
geometry=$(slurp) || exit 0 geometry=$(slurp) || exit 0
[[ -n $geometry ]] || exit 0 [[ -n $geometry ]] || exit 0
capture_args=(-g "$geometry") capture_args=(-g "$geometry")
elif [[ $mode == full ]]; then
for tool in feresectl python3; do
command -v "$tool" >/dev/null || fail "Required command not found: $tool"
done
# Resolve the active monitor before Satty can change keyboard focus.
# Never fall back to grim's all-monitor capture when IPC is unavailable.
output=$(feresectl get-outputs | python3 -c '
import json, sys
outputs = [output for output in json.load(sys.stdin) if output.get("enabled")]
focused = [output for output in outputs if output.get("focused")]
selected = focused if len(focused) == 1 else outputs if len(outputs) == 1 else []
if len(selected) != 1 or not selected[0].get("name"):
sys.exit(1)
print(selected[0]["name"])
') || fail 'Could not determine the active monitor. Use area selection or --all.'
capture_args=(-o "$output")
fi fi
pictures=$(xdg-user-dir PICTURES 2>/dev/null) || pictures="$HOME/Pictures" pictures=$(xdg-user-dir PICTURES 2>/dev/null) || pictures="$HOME/Pictures"
directory=${pictures:-$HOME/Pictures}/Screenshots directory=${pictures:-$HOME/Pictures}/Screenshots
@@ -48,13 +65,14 @@ trap '[[ -z $input ]] || rm -f -- "$input"; [[ -s $file ]] || rm -f -- "$file"'
input=$(mktemp "$XDG_RUNTIME_DIR/ferese-screenshot-XXXXXX.png") || fail 'Could not create the temporary capture.' input=$(mktemp "$XDG_RUNTIME_DIR/ferese-screenshot-XXXXXX.png") || fail 'Could not create the temporary capture.'
grim "${capture_args[@]}" "$input" || fail 'Could not capture the screen. Log back into Ferese after enabling screen capture.' grim "${capture_args[@]}" "$input" || fail 'Could not capture the screen. Log back into Ferese after enabling screen capture.'
# Enter saves the annotated image and copies it; Escape discards the capture. # Enter saves the annotated image and copies it; Escape discards the capture.
exec 9>&- # Keep the parent lock through editing. Close it only in Satty so clipboard
# helpers cannot inherit the lock and keep later screenshots blocked.
satty --filename "$input" --output-filename "$file" \ satty --filename "$input" --output-filename "$file" \
--fullscreen current-screen \ --fullscreen current-screen \
--copy-command 'wl-copy --type image/png' \ --copy-command 'wl-copy --type image/png' \
--save-after-copy \ --save-after-copy \
--actions-on-enter save-to-file,save-to-clipboard,exit \ --actions-on-enter save-to-file,save-to-clipboard,exit \
--actions-on-escape exit || fail 'Could not open the screenshot in Satty.' --actions-on-escape exit 9>&- || fail 'Could not open the screenshot in Satty.'
if [[ -s $file ]]; then if [[ -s $file ]]; then
printf '%s\n' "$file" printf '%s\n' "$file"
fi fi
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""Build Ferese's static website and handbook from the project Markdown docs."""
from pathlib import Path
from html import escape
import json
import re
import shutil
from urllib.parse import urlsplit
from markdown_it import MarkdownIt
ROOT = Path(__file__).resolve().parent.parent
OUTPUT = ROOT / 'build' / 'site'
REPO = 'https://github.com/borngraced/ferese'
PAGES = [
('index', 'Introduction', ROOT / 'site/content/index.md'),
('installation', 'Installation', ROOT / 'docs/installation.md'),
('configuration', 'Configuration', ROOT / 'docs/configuration.md'),
('desktop-widgets', 'Desktop widgets', ROOT / 'docs/desktop-widgets.md'),
('notifications', 'Notifications', ROOT / 'docs/notifications.md'),
]
md = MarkdownIt('commonmark', {'html': False}).enable('table')
OUTPUT.mkdir(parents=True, exist_ok=True)
for name in ('index.html', 'styles.css', 'app.js'):
shutil.copy2(ROOT / 'site' / name, OUTPUT / name)
(OUTPUT / 'assets').mkdir(exist_ok=True)
for name in ('ferese-desktop.png', 'ferese-monochrome.png'):
shutil.copy2(ROOT / 'docs/images' / name, OUTPUT / 'assets' / name)
shutil.copy2(ROOT / 'packaging/icons/ferese.svg', OUTPUT / 'assets/ferese.svg')
(OUTPUT / 'docs').mkdir(exist_ok=True)
(OUTPUT / '.nojekyll').touch()
search_index = []
def rewrite_link(href, source):
url = urlsplit(href)
if url.scheme or url.netloc or not url.path:
return href
basename = Path(url.path).stem
if url.path.endswith('.md') and basename in {page[0] for page in PAGES}:
return f'{basename}.html' + (f'#{url.fragment}' if url.fragment else '')
destination = (source.parent / url.path).resolve().relative_to(ROOT)
return f'{REPO}/blob/main/{destination}' + (f'#{url.fragment}' if url.fragment else '')
for page_number, (slug, title, source) in enumerate(PAGES):
tokens = md.parse(source.read_text())
toc, ids, sections = [], set(), []
current = None
for i, token in enumerate(tokens):
if token.type == 'heading_open':
heading = tokens[i + 1].content
base = re.sub(r'[^\w\s-]', '', heading.lower()).strip()
base = re.sub(r'[\s_]+', '-', base)
anchor = base
suffix = 1
while anchor in ids:
anchor = f'{base}-{suffix}'
suffix += 1
ids.add(anchor)
token.attrSet('id', anchor)
if token.tag != 'h1':
toc.append((anchor, heading))
current = {'page': title, 'heading': heading, 'url': f'{slug}.html#{anchor}', 'text': ''}
sections.append(current)
elif current and token.type in ('inline', 'fence', 'code_block'):
current['text'] += token.content + ' '
for child in token.children or []:
if child.type == 'link_open':
child.attrSet('href', rewrite_link(child.attrGet('href'), source))
search_index.extend(sections)
body = md.renderer.render(tokens, md.options, {})
body = body.replace('<table>', '<div class="table-scroll" tabindex="0" role="region" aria-label="Reference table"><table>').replace('</table>', '</table></div>')
navigation = ''.join(f'<a href="{s}.html"' + (' aria-current="page"' if s == slug else '') + f'>{escape(t)}</a>' for s, t, _ in PAGES)
contents = ''.join(f'<a href="#{anchor}">{escape(heading)}</a>' for anchor, heading in toc)
pager = ''
if page_number:
prev = PAGES[page_number - 1]
pager += f'<a href="{prev[0]}.html"><small>PREVIOUS</small>← {escape(prev[1])}</a>'
if page_number + 1 < len(PAGES):
nxt = PAGES[page_number + 1]
pager += f'<a href="{nxt[0]}.html"><small>NEXT</small>{escape(nxt[1])} →</a>'
doc = f'''<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#10151c"><title>{escape(title)} — Ferese handbook</title><meta name="description" content="{escape(title)} in the Ferese handbook: setup, configuration, and guides for your Wayland desktop.">
<link rel="icon" href="../assets/ferese.svg" type="image/svg+xml"><link rel="stylesheet" href="../styles.css"><script src="../app.js" defer></script></head>
<body class="docs-page"><a class="skip" href="#main">Skip to content</a>
<header class="site-header"><div class="header-inner"><a class="wordmark" href="../" aria-label="Ferese home"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 3h6v18H3zM11 3h10v6H11zM11 11h7v6h-7z"/></svg>ferese</a><nav aria-label="Main navigation"><a href="../">Overview</a><a href="./" aria-current="page">Documentation</a><a href="{REPO}">GitHub ↗</a></nav></div></header>
<div class="docs-layout"><aside class="docs-sidebar"><div class="docs-search" hidden><label class="sr-only" for="docs-search">Search the handbook</label><input id="docs-search" type="search" placeholder="Search the handbook…" autocomplete="off" aria-controls="search-results"><div class="search-results" id="search-results" hidden></div><p class="sr-only" id="search-status" role="status"></p></div><details open><summary>FERESE HANDBOOK</summary><nav aria-label="Documentation">{navigation}</nav></details><div class="sidebar-links"><a href="{REPO}/issues">Report an issue ↗</a><a href="../">Back to Ferese ←</a></div></aside>
<main class="docs-article" id="main"><div class="breadcrumb"><a href="./">Handbook</a><span aria-hidden="true">/</span><span>{escape(title)}</span></div><article class="prose">{body}</article><a class="edit-link" href="{REPO}/blob/main/{source.relative_to(ROOT)}">View this guide on GitHub ↗</a><nav class="docs-pager" aria-label="Previous and next guide">{pager}</nav></main>
<aside class="docs-toc" aria-label="On this page">{'<p>ON THIS PAGE</p><nav>' + contents + '</nav>' if contents else ''}</aside></div>
<footer class="site-footer page-width"><a class="wordmark" href="../">ferese</a><p>A beautiful, fluid Wayland desktop.</p><a href="{REPO}">Built in the open ↗</a></footer></body></html>'''
(OUTPUT / 'docs' / f'{slug}.html').write_text(doc)
(OUTPUT / 'docs/search-index.json').write_text(json.dumps(search_index, ensure_ascii=False))
print(f'Built website and {len(PAGES)} handbook pages at {OUTPUT}')
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""Exercise screenshot selection and locking without a Wayland desktop."""
import json
import os
from pathlib import Path
import subprocess
import tempfile
import time
import unittest
SCRIPT = Path(__file__).resolve().parents[2] / 'packaging/ferese-screenshot'
class ScreenshotTest(unittest.TestCase):
def test_active_output_all_outputs_and_editor_lock(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
bins = root / 'bin'
bins.mkdir()
env = dict(os.environ, XDG_RUNTIME_DIR=directory, TEST_ROOT=directory,
PATH=f'{bins}:{os.environ["PATH"]}')
(root / 'outputs.json').write_text(json.dumps([
{'name': 'eDP-1', 'enabled': True, 'focused': False},
{'name': 'HDMI-A-1', 'enabled': True, 'focused': True},
]))
scripts = {
'grim': '''#!/bin/bash
printf '%s\\n' "$@" >> "$TEST_ROOT/captures"
printf image > "${@: -1}"
''',
'feresectl': '#!/bin/bash\ncat "$TEST_ROOT/outputs.json"\n',
'notify-send': '#!/bin/bash\nexit 0\n',
'wl-copy': '#!/bin/bash\nexit 0\n',
'xdg-user-dir': '#!/bin/bash\nprintf "%s" "$TEST_ROOT/Pictures"\n',
'satty': '''#!/bin/bash
if [[ -e /proc/$$/fd/9 ]]; then touch "$TEST_ROOT/inherited-lock"; fi
printf 'editor\\n' >> "$TEST_ROOT/editors"
touch "$TEST_ROOT/ready"
while [[ ! -e "$TEST_ROOT/release" ]]; do sleep 0.02; done
''',
}
for name, contents in scripts.items():
path = bins / name
path.write_text(contents)
path.chmod(0o755)
def run(mode):
return subprocess.run(['bash', str(SCRIPT), mode], env=env,
capture_output=True, timeout=3)
first = subprocess.Popen(['bash', str(SCRIPT), '--full'], env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
deadline = time.monotonic() + 5
while not (root / 'ready').exists():
if time.monotonic() > deadline or first.poll() is not None:
self.fail('First screenshot did not open its editor')
time.sleep(0.02)
second = run('--full')
self.assertEqual(second.returncode, 0, second.stderr)
self.assertEqual((root / 'editors').read_text().splitlines(), ['editor'])
self.assertFalse((root / 'inherited-lock').exists())
(root / 'release').touch()
_, stderr = first.communicate(timeout=3)
self.assertEqual(first.returncode, 0, stderr)
third = run('--full')
self.assertEqual(third.returncode, 0, third.stderr)
self.assertEqual((root / 'editors').read_text().splitlines(), ['editor', 'editor'])
self.assertFalse(list((root / 'Pictures/Screenshots').glob('*.png')))
self.assertFalse(list(root.glob('ferese-screenshot-*.png')))
captures = (root / 'captures').read_text().splitlines()
self.assertEqual(captures[0:2], ['-o', 'HDMI-A-1'])
self.assertEqual(captures[3:5], ['-o', 'HDMI-A-1'])
all_monitors = run('--all')
self.assertEqual(all_monitors.returncode, 0, all_monitors.stderr)
captures = (root / 'captures').read_text().splitlines()
self.assertEqual(len(captures), 7) # --all passes only the filename
for outputs in [[], [{'enabled': True, 'name': 'A'}, {'enabled': True, 'name': 'B'}]]:
(root / 'outputs.json').write_text(json.dumps(outputs))
unavailable = run('--full')
self.assertNotEqual(unavailable.returncode, 0)
self.assertIn(b'Could not determine the active monitor', unavailable.stderr)
self.assertEqual((root / 'captures').read_text().splitlines(), captures)
(root / 'outputs.json').write_text(json.dumps([
{'name': 'eDP-1', 'enabled': False, 'focused': True},
{'name': 'HDMI-A-1', 'enabled': True, 'focused': False},
]))
single = run('--full')
self.assertEqual(single.returncode, 0, single.stderr)
self.assertEqual((root / 'captures').read_text().splitlines()[-3:-1], ['-o', 'HDMI-A-1'])
finally:
(root / 'release').touch()
if first.poll() is None:
first.terminate()
first.communicate(timeout=3)
if __name__ == '__main__':
unittest.main()
+7 -2
View File
@@ -500,7 +500,8 @@ impl cosmic::Application for App {
.push(self.label("Ferese", 16.)) .push(self.label("Ferese", 16.))
.width(Length::Fill), .width(Length::Fill),
) )
.on_drag(Message::DragWindow), .on_press(Message::DragWindow)
.interaction(cosmic::iced::mouse::Interaction::Grab),
) )
.push(widget::Space::new().height(14)) .push(widget::Space::new().height(14))
.push( .push(
@@ -865,7 +866,11 @@ impl cosmic::Application for App {
} }
} }
let mut content = column([]).spacing(16).push(heading); let mut content = column([]).spacing(16).push(
widget::mouse_area(heading.width(Length::Fill))
.on_press(Message::DragWindow)
.interaction(cosmic::iced::mouse::Interaction::Grab),
);
if let Some(error) = &self.error { if let Some(error) = &self.error {
content = content.push( content = content.push(
+38
View File
@@ -0,0 +1,38 @@
# 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.
There are no client frameworks, external fonts, or tracking scripts.
## Local preview
```sh
python3 -m venv .site-venv
.site-venv/bin/pip install -r site/requirements.txt
.site-venv/bin/python scripts/build-site.py
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.
- `site/index.html` and `site/styles.css`: homepage and shared visual design.
- `site/content/index.md`: handbook introduction.
- `docs/{installation,configuration,desktop-widgets,notifications}.md`: source guides.
- `scripts/build-site.py`: renders Markdown, resolves links, generates section
navigation, previous/next links, and the search index.
- `site/app.js`: handbook search and code copying. Reading and navigation work
without JavaScript. Search is local to the browser, with no third-party service.
## 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://borngraced.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.
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.
+130
View File
@@ -0,0 +1,130 @@
// Progressive enhancement for the handbook. Navigation and content work without JS.
const search = document.querySelector("#docs-search");
const results = document.querySelector("#search-results");
const status = document.querySelector("#search-status");
let indexPromise;
if (search) {
search.closest(".docs-search").hidden = false;
const loadIndex = () =>
(indexPromise ||= fetch("search-index.json")
.then((response) => {
if (!response.ok) throw new Error("Search index unavailable");
return response.json();
})
.catch((error) => {
indexPromise = null;
throw error;
}));
search.addEventListener("focus", () => {
loadIndex().catch(() => {});
});
search.addEventListener("input", async () => {
const query = search.value.trim().toLocaleLowerCase();
if (!query) {
results.hidden = true;
results.replaceChildren();
status.textContent = "";
return;
}
try {
const index = await loadIndex();
if (search.value.trim().toLocaleLowerCase() !== query) return;
const words = query.split(/\s+/);
const matches = index
.filter((item) =>
words.every((word) =>
`${item.page} ${item.heading} ${item.text}`
.toLocaleLowerCase()
.includes(word),
),
)
.sort(
(a, b) =>
Number(b.heading.toLocaleLowerCase().includes(query)) -
Number(a.heading.toLocaleLowerCase().includes(query)),
)
.slice(0, 10);
results.replaceChildren();
for (const item of matches) {
const link = document.createElement("a");
link.href = item.url;
const title = document.createElement("strong");
title.textContent = `${item.page} / ${item.heading}`;
const snippet = document.createElement("span");
const position = Math.max(
0,
item.text.toLocaleLowerCase().indexOf(words[0]) - 45,
);
snippet.textContent = `${position ? "…" : ""}${item.text.slice(position, position + 165)}${item.text.length > position + 165 ? "…" : ""}`;
link.append(title, snippet);
results.append(link);
}
if (!matches.length) {
const empty = document.createElement("p");
empty.textContent =
"No matching sections. Try “wallpaper”, “shortcuts”, or “notifications”.";
results.append(empty);
}
results.hidden = false;
status.textContent = `${matches.length} matching sections.`;
} catch {
if (search.value.trim().toLocaleLowerCase() !== query) return;
results.hidden = true;
status.textContent = "Search is unavailable. Browse the guides below.";
}
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
results.hidden = true;
search.focus();
}
if (
event.key === "/" &&
!["INPUT", "TEXTAREA"].includes(document.activeElement.tagName) &&
!event.ctrlKey &&
!event.metaKey &&
!event.altKey
) {
event.preventDefault();
search.focus();
}
if (
event.key === "ArrowDown" &&
document.activeElement === search &&
!results.hidden
) {
const first = results.querySelector("a");
if (first) {
event.preventDefault();
first.focus();
}
}
});
document.addEventListener("click", (event) => {
if (!event.target.closest(".docs-search")) results.hidden = true;
});
}
if (navigator.clipboard?.writeText) {
for (const block of document.querySelectorAll(".prose pre")) {
const button = document.createElement("button");
button.type = "button";
button.className = "code-copy";
button.textContent = "Copy";
button.setAttribute("aria-label", "Copy code example");
button.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(
block.querySelector("code").textContent,
);
button.textContent = "Copied";
if (status) status.textContent = "Code example copied.";
} catch {
button.textContent = "Select to copy";
if (status)
status.textContent =
"Copy failed. Select the code to copy it manually.";
}
});
block.append(button);
}
}
+59
View File
@@ -0,0 +1,59 @@
# The Ferese handbook
Ferese is a complete Wayland desktop built around personalization. Its custom shell, native Settings, live themes, and flexible layouts work together as one cohesive experience.
This handbook covers installing Ferese, finding your way around, and making the desktop your own.
## Start with your first session
1. Read [Installation](installation.md) for the supported setup, building, and recovery.
2. Open **Control Center → Settings** to choose a theme and configure your desktop.
3. Explore the [configuration reference](configuration.md) for layouts, shortcuts, window rules, and displays.
Want to try it before switching desktops? The [preview instructions](installation.md#preview-and-logs) cover running Ferese inside an existing Wayland session.
## Find your way around
Ferese supports scrolling columns, tree tiling, floating windows, and a workspace overview. These are the built-in keyboard shortcuts; all can be changed in Settings.
| Shortcut | Action |
| --- | --- |
| Super + Enter | Open a terminal |
| Super + H / J / K / L | Focus a window |
| Super + R | Cycle column width |
| Super + Tab | Open workspace and window overview |
| Super + M | Switch scrolling / tree layout |
| Super + Shift + S | Select a screenshot area |
Three-finger swipes move between workspaces and windows. Open **Control Center → Settings → Shortcuts** to change the defaults or assign gestures to other actions.
## Make it feel like yours
Use **Settings** for everyday customization. Choose an appearance preset, change the wallpaper, adjust shell and window corners, or add a clock and sticky notes through [Desktop widgets](desktop-widgets.md).
[Notifications](notifications.md) explains popup cards, history, Do Not Disturb, and app actions.
If you prefer a text editor, your configuration lives at:
```text
~/.config/ferese/config.kdl
```
Ferese reloads changes as you make them. An invalid edit keeps the last working configuration. Settings also preserves a backup of your previous save. See [Saving and validation](configuration.md#saving-and-validation).
## Keep a recovery path
Keep another desktop session available while testing hardware behavior. Save your work before logging out: `feresectl exit` and **Super + Shift + E** end the current session immediately.
The [recovery guide](installation.md#logout-and-recovery) explains logs, installed releases, and rollback.
## Work on Ferese
Ferese is built in Rust on Smithay. Use release builds when evaluating motion and startup performance. From the repository checkout:
```sh
cargo test --workspace --locked
cargo fmt --all --check
```
The [source repository](https://github.com/borngraced/ferese) is the place to explore implementation details and report issues. When reporting a problem, include how to reproduce it, whether you used the nested or DRM backend, and relevant logs.
+128
View File
@@ -0,0 +1,128 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#10151c" />
<meta
name="description"
content="Ferese is a beautiful, fluid Wayland desktop made to feel like yours. Explore the desktop, then get started with the documentation."
/>
<meta
property="og:title"
content="Ferese — A desktop that feels like yours"
/>
<meta
property="og:description"
content="A complete Wayland desktop built around personalization."
/>
<meta property="og:type" content="website" />
<meta
property="og:image"
content="https://borngraced.github.io/ferese/assets/ferese-desktop.png"
/>
<meta name="twitter:card" content="summary_large_image" />
<title>Ferese — A desktop that feels like yours</title>
<link rel="icon" href="assets/ferese.svg" type="image/svg+xml" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<header class="site-header">
<div class="header-inner">
<a class="wordmark" href="./" aria-label="Ferese home"
><svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 3h6v18H3zM11 3h10v6H11zM11 11h7v6h-7z" /></svg
>ferese</a
>
<nav aria-label="Main navigation">
<a href="./" aria-current="page">Overview</a
><a href="docs/">Documentation</a
><a href="https://github.com/borngraced/ferese"
>GitHub <span aria-hidden="true">↗</span></a
>
</nav>
</div>
</header>
<main id="main" class="simple-home">
<section class="product-intro page-width">
<h1>Ferese</h1>
<p class="product-tagline">
A beautiful, fluid Wayland desktop made to feel like yours.
</p>
<p class="product-description">
A complete Linux desktop with a custom shell, native settings,
scrolling and tiling layouts, and live-reloading themes.
</p>
<div class="product-links">
<a class="button" href="docs/installation.html">Install Ferese</a>
<a class="button secondary" href="docs/">Documentation</a>
<a class="source-link" href="https://github.com/borngraced/ferese"
>Source code ↗</a
>
</div>
</section>
<figure class="product-screenshot page-width">
<a
href="assets/ferese-desktop.png"
aria-label="View the full-resolution Ferese desktop screenshot"
>
<img
src="assets/ferese-desktop.png"
width="2880"
height="1800"
alt="Ferese Blue desktop with the default wallpaper, Settings, Control Center and clock widget"
fetchpriority="high"
/>
</a>
<figcaption>
Ferese Blue with Settings, Control Center, and the default wallpaper.
</figcaption>
</figure>
<section class="product-features page-width" aria-label="Features">
<article>
<h2>Flexible layouts</h2>
<p>
Scrolling columns, tree tiling, floating windows, and a workspace
overview. Use keyboard shortcuts or gestures to move around.
</p>
<a href="docs/configuration.html#layout">Configure layouts →</a>
</article>
<article>
<h2>A native desktop</h2>
<p>
A menu bar, Control Center, Settings, notifications, and desktop
widgets, designed to work together.
</p>
<a href="docs/">Explore the handbook →</a>
</article>
<article>
<h2>Make it yours</h2>
<p>
Choose your theme, wallpaper, colors, and motion. Change settings
through the app or a KDL config, with live reload.
</p>
<a href="docs/configuration.html#appearance">Customize Ferese →</a>
</article>
</section>
<section class="product-getting-started page-width">
<h2>Getting started</h2>
<p>
The <a href="docs/installation.html">installation guide</a> covers
Fedora with SDDM. You can also
<a href="docs/installation.html#preview-and-logs">preview Ferese</a>
inside an existing Wayland session before installing.
</p>
<p>
Built in Rust on Smithay. Development happens
<a href="https://github.com/borngraced/ferese">on GitHub</a>.
</p>
</section>
</main>
<footer class="site-footer page-width">
<a class="wordmark" href="./">ferese</a>
<p>A beautiful, fluid Wayland desktop.</p>
<a href="docs/">Read the handbook <span aria-hidden="true">↗</span></a>
</footer>
</body>
</html>
+2
View File
@@ -0,0 +1,2 @@
markdown-it-py==3.0.0
mdurl==0.1.2
+1141
View File
File diff suppressed because it is too large. Load diff