Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ separately in [CHANGELOG-DEPS.md](CHANGELOG-DEPS.md).

## Unreleased

- **System theme follows the Linux desktop.** Ferail reads GPUI’s portal-backed
appearance instead of requiring GNOME settings schemas, and updates open
windows when the desktop switches between light and dark.

- **Going up to a parent folder selects the folder you came from and scrolls
it into view, whichever way you go up.** Backspace and Back already did
this; clicking a breadcrumb segment, a folder in the sidebar tree, or typing
Expand Down
46 changes: 34 additions & 12 deletions crates/ferail-gpui/src/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,31 +221,53 @@ pub fn run_gui(args: screenshot::Args) {
// 1. `--theme {light,dark}` CLI flag
// 2. `FERAIL_THEME` env var (light / dark / system)
// 3. Persisted `theme_pref` in app_state
// 4. macOS Appearance via `system_is_dark()`
// 4. System appearance
let env_theme = std::env::var("FERAIL_THEME").ok().map(|s| s.to_lowercase());
let persisted_theme = crate::app_state::load().theme_pref;
let resolve_string = |s: &str| -> Option<gpui_component::ThemeMode> {
match s {
"light" => Some(gpui_component::ThemeMode::Light),
"dark" => Some(gpui_component::ThemeMode::Dark),
"system" | "auto" => Some(if crate::platform_shell::system_is_dark() {
gpui_component::ThemeMode::Dark
} else {
gpui_component::ThemeMode::Light
}),
"system" | "auto" => Some(crate::settings::ThemePref::System.resolve(cx)),
_ => None,
}
};
let mode = theme_mode
.or_else(|| env_theme.as_deref().and_then(resolve_string))
.or_else(|| persisted_theme.as_deref().and_then(resolve_string))
.unwrap_or_else(|| {
if crate::platform_shell::system_is_dark() {
gpui_component::ThemeMode::Dark
} else {
gpui_component::ThemeMode::Light
.unwrap_or_else(|| crate::settings::ThemePref::System.resolve(cx));
#[cfg(target_os = "linux")]
{
let configured = env_theme
.as_deref()
.filter(|value| matches!(*value, "light" | "dark" | "system" | "auto"))
.or(persisted_theme.as_deref());
cx.set_global(crate::settings::FollowSystemTheme(
theme_mode.is_none() && !matches!(configured, Some("light" | "dark")),
));
fn sync_system_theme(window: &mut gpui::Window, cx: &mut gpui::App) {
if cx
.try_global::<crate::settings::FollowSystemTheme>()
.is_some_and(|follow| follow.0)
{
let mode: gpui_component::ThemeMode = window.appearance().into();
if Theme::global(cx).mode != mode {
Theme::change(mode, Some(window), cx);
crate::shell::set_system_theme_pending(matches!(
mode,
gpui_component::ThemeMode::Dark
));
}
}
});
}
cx.observe_new::<gpui_component::Root>(|_, window, cx| {
if let Some(window) = window {
sync_system_theme(window, cx);
window.observe_window_appearance(sync_system_theme).detach();
}
})
.detach();
}
Theme::change(mode, None, cx);
// Sync native chrome (titlebars, traffic lights, menus) to the
// app theme so secondary windows don't show system-dark chrome
Expand Down
29 changes: 23 additions & 6 deletions crates/ferail-gpui/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ pub fn category_from_arg(arg: Option<&str>) -> SettingsCategory {
// Theme preference: external API
// =============================================================================

#[cfg(target_os = "linux")]
pub(crate) struct FollowSystemTheme(pub bool);
#[cfg(target_os = "linux")]
impl gpui::Global for FollowSystemTheme {}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ThemePref {
Light,
Expand All @@ -146,15 +151,22 @@ impl ThemePref {
}
}

fn resolve(self) -> ThemeMode {
pub(crate) fn resolve(self, _cx: &App) -> ThemeMode {
match self {
ThemePref::Light => ThemeMode::Light,
ThemePref::Dark => ThemeMode::Dark,
ThemePref::System => {
if crate::platform_shell::system_is_dark() {
ThemeMode::Dark
} else {
ThemeMode::Light
#[cfg(target_os = "linux")]
{
_cx.window_appearance().into()
}
#[cfg(not(target_os = "linux"))]
{
if crate::platform_shell::system_is_dark() {
ThemeMode::Dark
} else {
ThemeMode::Light
}
}
}
}
Expand Down Expand Up @@ -2611,7 +2623,12 @@ fn theme_tile(pref: ThemePref) -> impl IntoElement {
// when given a Window. `cx.refresh_windows()` propagates
// the same change to any other open window (e.g. the
// main Shell while Settings is open in a second window).
let resolved = pref.resolve();
let resolved = pref.resolve(cx);
#[cfg(target_os = "linux")]
{
cx.set_global(FollowSystemTheme(pref == ThemePref::System));
crate::shell::set_system_theme_pending(matches!(resolved, ThemeMode::Dark));
}
Theme::change(resolved, Some(window), cx);
// Match native chrome across all windows to the new theme.
crate::platform_shell::set_app_appearance(matches!(
Expand Down